1/* 2 * Rewritten Python launcher for Windows 3 * 4 * This new rewrite properly handles PEP 514 and allows any registered Python 5 * runtime to be launched. It also enables auto-install of versions when they 6 * are requested but no installation can be found. 7 */ 8 9#define __STDC_WANT_LIB_EXT1__ 1 10 11#include <windows.h> 12#include <pathcch.h> 13#include <fcntl.h> 14#include <io.h> 15#include <shlobj.h> 16#include <stdio.h> 17#include <stdbool.h> 18#include <tchar.h> 19#include <assert.h> 20 21#define MS_WINDOWS 22#include "patchlevel.h" 23 24#define MAXLEN PATHCCH_MAX_CCH 25#define MSGSIZE 1024 26 27#define RC_NO_STD_HANDLES 100 28#define RC_CREATE_PROCESS 101 29#define RC_BAD_VIRTUAL_PATH 102 30#define RC_NO_PYTHON 103 31#define RC_NO_MEMORY 104 32#define RC_NO_SCRIPT 105 33#define RC_NO_VENV_CFG 106 34#define RC_BAD_VENV_CFG 107 35#define RC_NO_COMMANDLINE 108 36#define RC_INTERNAL_ERROR 109 37#define RC_DUPLICATE_ITEM 110 38#define RC_INSTALLING 111 39#define RC_NO_PYTHON_AT_ALL 112 40#define RC_NO_SHEBANG 113 41#define RC_RECURSIVE_SHEBANG 114 42 43static FILE * log_fp = NULL; 44 45void 46debug(wchar_t * format, ...) 47{ 48 va_list va; 49 50 if (log_fp != NULL) { 51 wchar_t buffer[MAXLEN]; 52 int r = 0; 53 va_start(va, format); 54 r = vswprintf_s(buffer, MAXLEN, format, va); 55 va_end(va); 56 57 if (r <= 0) { 58 return; 59 } 60 fputws(buffer, log_fp); 61 while (r && isspace(buffer[r])) { 62 buffer[r--] = L'\0'; 63 } 64 if (buffer[0]) { 65 OutputDebugStringW(buffer); 66 } 67 } 68} 69 70 71void 72formatWinerror(int rc, wchar_t * message, int size) 73{ 74 FormatMessageW( 75 FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, 76 NULL, rc, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), 77 message, size, NULL); 78} 79 80 81void 82winerror(int err, wchar_t * format, ... ) 83{ 84 va_list va; 85 wchar_t message[MSGSIZE]; 86 wchar_t win_message[MSGSIZE]; 87 int len; 88 89 if (err == 0) { 90 err = GetLastError(); 91 } 92 93 va_start(va, format); 94 len = _vsnwprintf_s(message, MSGSIZE, _TRUNCATE, format, va); 95 va_end(va); 96 97 formatWinerror(err, win_message, MSGSIZE); 98 if (len >= 0) { 99 _snwprintf_s(&message[len], MSGSIZE - len, _TRUNCATE, L": %s", 100 win_message); 101 } 102 103#if !defined(_WINDOWS) 104 fwprintf(stderr, L"%s\n", message); 105#else 106 MessageBoxW(NULL, message, L"Python Launcher is sorry to say ...", 107 MB_OK); 108#endif 109} 110 111 112void 113error(wchar_t * format, ... ) 114{ 115 va_list va; 116 wchar_t message[MSGSIZE]; 117 118 va_start(va, format); 119 _vsnwprintf_s(message, MSGSIZE, _TRUNCATE, format, va); 120 va_end(va); 121 122#if !defined(_WINDOWS) 123 fwprintf(stderr, L"%s\n", message); 124#else 125 MessageBoxW(NULL, message, L"Python Launcher is sorry to say ...", 126 MB_OK); 127#endif 128} 129 130 131typedef BOOL (*PIsWow64Process2)(HANDLE, USHORT*, USHORT*); 132 133 134USHORT 135_getNativeMachine() 136{ 137 static USHORT _nativeMachine = IMAGE_FILE_MACHINE_UNKNOWN; 138 if (_nativeMachine == IMAGE_FILE_MACHINE_UNKNOWN) { 139 USHORT processMachine; 140 HMODULE kernel32 = GetModuleHandleW(L"kernel32.dll"); 141 PIsWow64Process2 IsWow64Process2 = kernel32 ? 142 (PIsWow64Process2)GetProcAddress(kernel32, "IsWow64Process2") : 143 NULL; 144 if (!IsWow64Process2) { 145 BOOL wow64Process; 146 if (!IsWow64Process(NULL, &wow64Process)) { 147 winerror(0, L"Checking process type"); 148 } else if (wow64Process) { 149 // We should always be a 32-bit executable, so if running 150 // under emulation, it must be a 64-bit host. 151 _nativeMachine = IMAGE_FILE_MACHINE_AMD64; 152 } else { 153 // Not running under emulation, and an old enough OS to not 154 // have IsWow64Process2, so assume it's x86. 155 _nativeMachine = IMAGE_FILE_MACHINE_I386; 156 } 157 } else if (!IsWow64Process2(NULL, &processMachine, &_nativeMachine)) { 158 winerror(0, L"Checking process type"); 159 } 160 } 161 return _nativeMachine; 162} 163 164 165bool 166isAMD64Host() 167{ 168 return _getNativeMachine() == IMAGE_FILE_MACHINE_AMD64; 169} 170 171 172bool 173isARM64Host() 174{ 175 return _getNativeMachine() == IMAGE_FILE_MACHINE_ARM64; 176} 177 178 179bool 180isEnvVarSet(const wchar_t *name) 181{ 182 /* only looking for non-empty, which means at least one character 183 and the null terminator */ 184 return GetEnvironmentVariableW(name, NULL, 0) >= 2; 185} 186 187 188bool 189join(wchar_t *buffer, size_t bufferLength, const wchar_t *fragment) 190{ 191 if (SUCCEEDED(PathCchCombineEx(buffer, bufferLength, buffer, fragment, PATHCCH_ALLOW_LONG_PATHS))) { 192 return true; 193 } 194 return false; 195} 196 197 198int 199_compare(const wchar_t *x, int xLen, const wchar_t *y, int yLen) 200{ 201 // Empty strings sort first 202 if (!x || !xLen) { 203 return (!y || !yLen) ? 0 : -1; 204 } else if (!y || !yLen) { 205 return 1; 206 } 207 switch (CompareStringEx( 208 LOCALE_NAME_INVARIANT, NORM_IGNORECASE | SORT_DIGITSASNUMBERS, 209 x, xLen, y, yLen, 210 NULL, NULL, 0 211 )) { 212 case CSTR_LESS_THAN: 213 return -1; 214 case CSTR_EQUAL: 215 return 0; 216 case CSTR_GREATER_THAN: 217 return 1; 218 default: 219 winerror(0, L"Error comparing '%.*s' and '%.*s' (compare)", xLen, x, yLen, y); 220 return -1; 221 } 222} 223 224 225int 226_compareArgument(const wchar_t *x, int xLen, const wchar_t *y, int yLen) 227{ 228 // Empty strings sort first 229 if (!x || !xLen) { 230 return (!y || !yLen) ? 0 : -1; 231 } else if (!y || !yLen) { 232 return 1; 233 } 234 switch (CompareStringEx( 235 LOCALE_NAME_INVARIANT, 0, 236 x, xLen, y, yLen, 237 NULL, NULL, 0 238 )) { 239 case CSTR_LESS_THAN: 240 return -1; 241 case CSTR_EQUAL: 242 return 0; 243 case CSTR_GREATER_THAN: 244 return 1; 245 default: 246 winerror(0, L"Error comparing '%.*s' and '%.*s' (compareArgument)", xLen, x, yLen, y); 247 return -1; 248 } 249} 250 251int 252_comparePath(const wchar_t *x, int xLen, const wchar_t *y, int yLen) 253{ 254 // Empty strings sort first 255 if (!x || !xLen) { 256 return !y || !yLen ? 0 : -1; 257 } else if (!y || !yLen) { 258 return 1; 259 } 260 switch (CompareStringOrdinal(x, xLen, y, yLen, TRUE)) { 261 case CSTR_LESS_THAN: 262 return -1; 263 case CSTR_EQUAL: 264 return 0; 265 case CSTR_GREATER_THAN: 266 return 1; 267 default: 268 winerror(0, L"Error comparing '%.*s' and '%.*s' (comparePath)", xLen, x, yLen, y); 269 return -1; 270 } 271} 272 273 274bool 275_startsWith(const wchar_t *x, int xLen, const wchar_t *y, int yLen) 276{ 277 if (!x || !y) { 278 return false; 279 } 280 yLen = yLen < 0 ? (int)wcsnlen_s(y, MAXLEN) : yLen; 281 xLen = xLen < 0 ? (int)wcsnlen_s(x, MAXLEN) : xLen; 282 return xLen >= yLen && 0 == _compare(x, yLen, y, yLen); 283} 284 285 286bool 287_startsWithArgument(const wchar_t *x, int xLen, const wchar_t *y, int yLen) 288{ 289 if (!x || !y) { 290 return false; 291 } 292 yLen = yLen < 0 ? (int)wcsnlen_s(y, MAXLEN) : yLen; 293 xLen = xLen < 0 ? (int)wcsnlen_s(x, MAXLEN) : xLen; 294 return xLen >= yLen && 0 == _compareArgument(x, yLen, y, yLen); 295} 296 297 298// Unlike regular startsWith, this function requires that the following 299// character is either NULL (that is, the entire string matches) or is one of 300// the characters in 'separators'. 301bool 302_startsWithSeparated(const wchar_t *x, int xLen, const wchar_t *y, int yLen, const wchar_t *separators) 303{ 304 if (!x || !y) { 305 return false; 306 } 307 yLen = yLen < 0 ? (int)wcsnlen_s(y, MAXLEN) : yLen; 308 xLen = xLen < 0 ? (int)wcsnlen_s(x, MAXLEN) : xLen; 309 if (xLen < yLen) { 310 return false; 311 } 312 if (xLen == yLen) { 313 return 0 == _compare(x, xLen, y, yLen); 314 } 315 return separators && 316 0 == _compare(x, yLen, y, yLen) && 317 wcschr(separators, x[yLen]) != NULL; 318} 319 320 321 322/******************************************************************************\ 323 *** HELP TEXT *** 324\******************************************************************************/ 325 326 327int 328showHelpText(wchar_t ** argv) 329{ 330 // The help text is stored in launcher-usage.txt, which is compiled into 331 // the launcher and loaded at runtime if needed. 332 // 333 // The file must be UTF-8. There are two substitutions: 334 // %ls - PY_VERSION (as wchar_t*) 335 // %ls - argv[0] (as wchar_t*) 336 HRSRC res = FindResourceExW(NULL, L"USAGE", MAKEINTRESOURCE(1), MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL)); 337 HGLOBAL resData = res ? LoadResource(NULL, res) : NULL; 338 const char *usage = resData ? (const char*)LockResource(resData) : NULL; 339 if (usage == NULL) { 340 winerror(0, L"Unable to load usage text"); 341 return RC_INTERNAL_ERROR; 342 } 343 344 DWORD cbData = SizeofResource(NULL, res); 345 DWORD cchUsage = MultiByteToWideChar(CP_UTF8, 0, usage, cbData, NULL, 0); 346 if (!cchUsage) { 347 winerror(0, L"Unable to preprocess usage text"); 348 return RC_INTERNAL_ERROR; 349 } 350 351 cchUsage += 1; 352 wchar_t *wUsage = (wchar_t*)malloc(cchUsage * sizeof(wchar_t)); 353 cchUsage = MultiByteToWideChar(CP_UTF8, 0, usage, cbData, wUsage, cchUsage); 354 if (!cchUsage) { 355 winerror(0, L"Unable to preprocess usage text"); 356 free((void *)wUsage); 357 return RC_INTERNAL_ERROR; 358 } 359 // Ensure null termination 360 wUsage[cchUsage] = L'\0'; 361 362 fwprintf(stdout, wUsage, (L"" PY_VERSION), argv[0]); 363 fflush(stdout); 364 365 free((void *)wUsage); 366 367 return 0; 368} 369 370 371/******************************************************************************\ 372 *** SEARCH INFO *** 373\******************************************************************************/ 374 375 376struct _SearchInfoBuffer { 377 struct _SearchInfoBuffer *next; 378 wchar_t buffer[0]; 379}; 380 381 382typedef struct { 383 // the original string, managed by the OS 384 const wchar_t *originalCmdLine; 385 // pointer into the cmdline to mark what we've consumed 386 const wchar_t *restOfCmdLine; 387 // if known/discovered, the full executable path of our runtime 388 const wchar_t *executablePath; 389 // pointer and length into cmdline for the file to check for a 390 // shebang line, if any. Length can be -1 if the string is null 391 // terminated. 392 const wchar_t *scriptFile; 393 int scriptFileLength; 394 // pointer and length into cmdline or a static string with the 395 // name of the target executable. Length can be -1 if the string 396 // is null terminated. 397 const wchar_t *executable; 398 int executableLength; 399 // pointer and length into a string with additional interpreter 400 // arguments to include before restOfCmdLine. Length can be -1 if 401 // the string is null terminated. 402 const wchar_t *executableArgs; 403 int executableArgsLength; 404 // pointer and length into cmdline or a static string with the 405 // company name for PEP 514 lookup. Length can be -1 if the string 406 // is null terminated. 407 const wchar_t *company; 408 int companyLength; 409 // pointer and length into cmdline or a static string with the 410 // tag for PEP 514 lookup. Length can be -1 if the string is 411 // null terminated. 412 const wchar_t *tag; 413 int tagLength; 414 // if true, treats 'tag' as a non-PEP 514 filter 415 bool oldStyleTag; 416 // if true, ignores 'tag' when a high priority environment is found 417 // gh-92817: This is currently set when a tag is read from configuration or 418 // the environment, rather than the command line or a shebang line, and the 419 // only currently possible high priority environment is an active virtual 420 // environment 421 bool lowPriorityTag; 422 // if true, allow PEP 514 lookup to override 'executable' 423 bool allowExecutableOverride; 424 // if true, allow a nearby pyvenv.cfg to locate the executable 425 bool allowPyvenvCfg; 426 // if true, allow defaults (env/py.ini) to clarify/override tags 427 bool allowDefaults; 428 // if true, prefer windowed (console-less) executable 429 bool windowed; 430 // if true, only list detected runtimes without launching 431 bool list; 432 // if true, only list detected runtimes with paths without launching 433 bool listPaths; 434 // if true, display help message before contiuning 435 bool help; 436 // if set, limits search to registry keys with the specified Company 437 // This is intended for debugging and testing only 438 const wchar_t *limitToCompany; 439 // dynamically allocated buffers to free later 440 struct _SearchInfoBuffer *_buffer; 441} SearchInfo; 442 443 444wchar_t * 445allocSearchInfoBuffer(SearchInfo *search, int wcharCount) 446{ 447 struct _SearchInfoBuffer *buffer = (struct _SearchInfoBuffer*)malloc( 448 sizeof(struct _SearchInfoBuffer) + 449 wcharCount * sizeof(wchar_t) 450 ); 451 if (!buffer) { 452 return NULL; 453 } 454 buffer->next = search->_buffer; 455 search->_buffer = buffer; 456 return buffer->buffer; 457} 458 459 460void 461freeSearchInfo(SearchInfo *search) 462{ 463 struct _SearchInfoBuffer *b = search->_buffer; 464 search->_buffer = NULL; 465 while (b) { 466 struct _SearchInfoBuffer *nextB = b->next; 467 free((void *)b); 468 b = nextB; 469 } 470} 471 472 473void 474_debugStringAndLength(const wchar_t *s, int len, const wchar_t *name) 475{ 476 if (!s) { 477 debug(L"%s: (null)\n", name); 478 } else if (len == 0) { 479 debug(L"%s: (empty)\n", name); 480 } else if (len < 0) { 481 debug(L"%s: %s\n", name, s); 482 } else { 483 debug(L"%s: %.*ls\n", name, len, s); 484 } 485} 486 487 488void 489dumpSearchInfo(SearchInfo *search) 490{ 491 if (!log_fp) { 492 return; 493 } 494 495#define DEBUGNAME(s) L"SearchInfo." ## s 496#define DEBUG(s) debug(DEBUGNAME(#s) L": %s\n", (search->s) ? (search->s) : L"(null)") 497#define DEBUG_2(s, sl) _debugStringAndLength((search->s), (search->sl), DEBUGNAME(#s)) 498#define DEBUG_BOOL(s) debug(DEBUGNAME(#s) L": %s\n", (search->s) ? L"True" : L"False") 499 DEBUG(originalCmdLine); 500 DEBUG(restOfCmdLine); 501 DEBUG(executablePath); 502 DEBUG_2(scriptFile, scriptFileLength); 503 DEBUG_2(executable, executableLength); 504 DEBUG_2(executableArgs, executableArgsLength); 505 DEBUG_2(company, companyLength); 506 DEBUG_2(tag, tagLength); 507 DEBUG_BOOL(oldStyleTag); 508 DEBUG_BOOL(lowPriorityTag); 509 DEBUG_BOOL(allowDefaults); 510 DEBUG_BOOL(allowExecutableOverride); 511 DEBUG_BOOL(windowed); 512 DEBUG_BOOL(list); 513 DEBUG_BOOL(listPaths); 514 DEBUG_BOOL(help); 515 DEBUG(limitToCompany); 516#undef DEBUG_BOOL 517#undef DEBUG_2 518#undef DEBUG 519#undef DEBUGNAME 520} 521 522 523int 524findArgv0Length(const wchar_t *buffer, int bufferLength) 525{ 526 // Note: this implements semantics that are only valid for argv0. 527 // Specifically, there is no escaping of quotes, and quotes within 528 // the argument have no effect. A quoted argv0 must start and end 529 // with a double quote character; otherwise, it ends at the first 530 // ' ' or '\t'. 531 int quoted = buffer[0] == L'"'; 532 for (int i = 1; bufferLength < 0 || i < bufferLength; ++i) { 533 switch (buffer[i]) { 534 case L'\0': 535 return i; 536 case L' ': 537 case L'\t': 538 if (!quoted) { 539 return i; 540 } 541 break; 542 case L'"': 543 if (quoted) { 544 return i + 1; 545 } 546 break; 547 } 548 } 549 return bufferLength; 550} 551 552 553const wchar_t * 554findArgv0End(const wchar_t *buffer, int bufferLength) 555{ 556 return &buffer[findArgv0Length(buffer, bufferLength)]; 557} 558 559 560/******************************************************************************\ 561 *** COMMAND-LINE PARSING *** 562\******************************************************************************/ 563 564 565int 566parseCommandLine(SearchInfo *search) 567{ 568 if (!search || !search->originalCmdLine) { 569 return RC_NO_COMMANDLINE; 570 } 571 572 const wchar_t *argv0End = findArgv0End(search->originalCmdLine, -1); 573 const wchar_t *tail = argv0End; // will be start of the executable name 574 const wchar_t *end = argv0End; // will be end of the executable name 575 search->restOfCmdLine = argv0End; // will be first space after argv0 576 while (--tail != search->originalCmdLine) { 577 if (*tail == L'"' && end == argv0End) { 578 // Move the "end" up to the quote, so we also allow moving for 579 // a period later on. 580 end = argv0End = tail; 581 } else if (*tail == L'.' && end == argv0End) { 582 end = tail; 583 } else if (*tail == L'\\' || *tail == L'/') { 584 ++tail; 585 break; 586 } 587 } 588 if (tail == search->originalCmdLine && tail[0] == L'"') { 589 ++tail; 590 } 591 // Without special cases, we can now fill in the search struct 592 int tailLen = (int)(end ? (end - tail) : wcsnlen_s(tail, MAXLEN)); 593 search->executableLength = -1; 594 595 // Our special cases are as follows 596#define MATCHES(s) (0 == _comparePath(tail, tailLen, (s), -1)) 597#define STARTSWITH(s) _startsWith(tail, tailLen, (s), -1) 598 if (MATCHES(L"py")) { 599 search->executable = L"python.exe"; 600 search->allowExecutableOverride = true; 601 search->allowDefaults = true; 602 } else if (MATCHES(L"pyw")) { 603 search->executable = L"pythonw.exe"; 604 search->allowExecutableOverride = true; 605 search->allowDefaults = true; 606 search->windowed = true; 607 } else if (MATCHES(L"py_d")) { 608 search->executable = L"python_d.exe"; 609 search->allowExecutableOverride = true; 610 search->allowDefaults = true; 611 } else if (MATCHES(L"pyw_d")) { 612 search->executable = L"pythonw_d.exe"; 613 search->allowExecutableOverride = true; 614 search->allowDefaults = true; 615 search->windowed = true; 616 } else if (STARTSWITH(L"python3")) { 617 search->executable = L"python.exe"; 618 search->tag = &tail[6]; 619 search->tagLength = tailLen - 6; 620 search->allowExecutableOverride = true; 621 search->oldStyleTag = true; 622 search->allowPyvenvCfg = true; 623 } else if (STARTSWITH(L"pythonw3")) { 624 search->executable = L"pythonw.exe"; 625 search->tag = &tail[7]; 626 search->tagLength = tailLen - 7; 627 search->allowExecutableOverride = true; 628 search->oldStyleTag = true; 629 search->allowPyvenvCfg = true; 630 search->windowed = true; 631 } else { 632 search->executable = tail; 633 search->executableLength = tailLen; 634 search->allowPyvenvCfg = true; 635 } 636#undef STARTSWITH 637#undef MATCHES 638 639 // First argument might be one of our options. If so, consume it, 640 // update flags and then set restOfCmdLine. 641 const wchar_t *arg = search->restOfCmdLine; 642 while(*arg && isspace(*arg)) { ++arg; } 643#define MATCHES(s) (0 == _compareArgument(arg, argLen, (s), -1)) 644#define STARTSWITH(s) _startsWithArgument(arg, argLen, (s), -1) 645 if (*arg && *arg == L'-' && *++arg) { 646 tail = arg; 647 while (*tail && !isspace(*tail)) { ++tail; } 648 int argLen = (int)(tail - arg); 649 if (argLen > 0) { 650 if (STARTSWITH(L"2") || STARTSWITH(L"3")) { 651 // All arguments starting with 2 or 3 are assumed to be version tags 652 search->tag = arg; 653 search->tagLength = argLen; 654 search->oldStyleTag = true; 655 search->restOfCmdLine = tail; 656 } else if (STARTSWITH(L"V:") || STARTSWITH(L"-version:")) { 657 // Arguments starting with 'V:' specify company and/or tag 658 const wchar_t *argStart = wcschr(arg, L':') + 1; 659 const wchar_t *tagStart = wcschr(argStart, L'/') ; 660 if (tagStart) { 661 search->company = argStart; 662 search->companyLength = (int)(tagStart - argStart); 663 search->tag = tagStart + 1; 664 } else { 665 search->tag = argStart; 666 } 667 search->tagLength = (int)(tail - search->tag); 668 search->allowDefaults = false; 669 search->restOfCmdLine = tail; 670 } else if (MATCHES(L"0") || MATCHES(L"-list")) { 671 search->list = true; 672 search->restOfCmdLine = tail; 673 } else if (MATCHES(L"0p") || MATCHES(L"-list-paths")) { 674 search->listPaths = true; 675 search->restOfCmdLine = tail; 676 } else if (MATCHES(L"h") || MATCHES(L"-help")) { 677 search->help = true; 678 // Do not update restOfCmdLine so that we trigger the help 679 // message from whichever interpreter we select 680 } 681 } 682 } 683#undef STARTSWITH 684#undef MATCHES 685 686 // Might have a script filename. If it looks like a filename, add 687 // it to the SearchInfo struct for later reference. 688 arg = search->restOfCmdLine; 689 while(*arg && isspace(*arg)) { ++arg; } 690 if (*arg && *arg != L'-') { 691 search->scriptFile = arg; 692 if (*arg == L'"') { 693 ++search->scriptFile; 694 while (*++arg && *arg != L'"') { } 695 } else { 696 while (*arg && !isspace(*arg)) { ++arg; } 697 } 698 search->scriptFileLength = (int)(arg - search->scriptFile); 699 } 700 701 return 0; 702} 703 704 705int 706_decodeShebang(SearchInfo *search, const char *buffer, int bufferLength, bool onlyUtf8, wchar_t **decoded, int *decodedLength) 707{ 708 DWORD cp = CP_UTF8; 709 int wideLen = MultiByteToWideChar(cp, MB_ERR_INVALID_CHARS, buffer, bufferLength, NULL, 0); 710 if (!wideLen) { 711 cp = CP_ACP; 712 wideLen = MultiByteToWideChar(cp, MB_ERR_INVALID_CHARS, buffer, bufferLength, NULL, 0); 713 if (!wideLen) { 714 debug(L"# Failed to decode shebang line (0x%08X)\n", GetLastError()); 715 return RC_BAD_VIRTUAL_PATH; 716 } 717 } 718 wchar_t *b = allocSearchInfoBuffer(search, wideLen + 1); 719 if (!b) { 720 return RC_NO_MEMORY; 721 } 722 wideLen = MultiByteToWideChar(cp, 0, buffer, bufferLength, b, wideLen + 1); 723 if (!wideLen) { 724 debug(L"# Failed to decode shebang line (0x%08X)\n", GetLastError()); 725 return RC_BAD_VIRTUAL_PATH; 726 } 727 b[wideLen] = L'\0'; 728 *decoded = b; 729 *decodedLength = wideLen; 730 return 0; 731} 732 733 734bool 735_shebangStartsWith(const wchar_t *buffer, int bufferLength, const wchar_t *prefix, const wchar_t **rest, int *firstArgumentLength) 736{ 737 int prefixLength = (int)wcsnlen_s(prefix, MAXLEN); 738 if (bufferLength < prefixLength || !_startsWithArgument(buffer, bufferLength, prefix, prefixLength)) { 739 return false; 740 } 741 if (rest) { 742 *rest = &buffer[prefixLength]; 743 } 744 if (firstArgumentLength) { 745 int i = prefixLength; 746 while (i < bufferLength && !isspace(buffer[i])) { 747 i += 1; 748 } 749 *firstArgumentLength = i - prefixLength; 750 } 751 return true; 752} 753 754 755int 756searchPath(SearchInfo *search, const wchar_t *shebang, int shebangLength) 757{ 758 if (isEnvVarSet(L"PYLAUNCHER_NO_SEARCH_PATH")) { 759 return RC_NO_SHEBANG; 760 } 761 762 wchar_t *command; 763 int commandLength; 764 if (!_shebangStartsWith(shebang, shebangLength, L"/usr/bin/env ", &command, &commandLength)) { 765 return RC_NO_SHEBANG; 766 } 767 768 if (!commandLength || commandLength == MAXLEN) { 769 return RC_BAD_VIRTUAL_PATH; 770 } 771 772 int lastDot = commandLength; 773 while (lastDot > 0 && command[lastDot] != L'.') { 774 lastDot -= 1; 775 } 776 if (!lastDot) { 777 lastDot = commandLength; 778 } 779 780 wchar_t filename[MAXLEN]; 781 if (wcsncpy_s(filename, MAXLEN, command, lastDot)) { 782 return RC_BAD_VIRTUAL_PATH; 783 } 784 785 const wchar_t *ext = L".exe"; 786 // If the command already has an extension, we do not want to add it again 787 if (!lastDot || _comparePath(&filename[lastDot], -1, ext, -1)) { 788 if (wcscat_s(filename, MAXLEN, L".exe")) { 789 return RC_BAD_VIRTUAL_PATH; 790 } 791 } 792 793 wchar_t pathVariable[MAXLEN]; 794 int n = GetEnvironmentVariableW(L"PATH", pathVariable, MAXLEN); 795 if (!n) { 796 if (GetLastError() == ERROR_ENVVAR_NOT_FOUND) { 797 return RC_NO_SHEBANG; 798 } 799 winerror(0, L"Failed to read PATH\n", filename); 800 return RC_INTERNAL_ERROR; 801 } 802 803 wchar_t buffer[MAXLEN]; 804 n = SearchPathW(pathVariable, filename, NULL, MAXLEN, buffer, NULL); 805 if (!n) { 806 if (GetLastError() == ERROR_FILE_NOT_FOUND) { 807 debug(L"# Did not find %s on PATH\n", filename); 808 // If we didn't find it on PATH, let normal handling take over 809 return RC_NO_SHEBANG; 810 } 811 // Other errors should cause us to break 812 winerror(0, L"Failed to find %s on PATH\n", filename); 813 return RC_BAD_VIRTUAL_PATH; 814 } 815 816 // Check that we aren't going to call ourselves again 817 // If we are, pretend there was no shebang and let normal handling take over 818 if (GetModuleFileNameW(NULL, filename, MAXLEN) && 819 0 == _comparePath(filename, -1, buffer, -1)) { 820 debug(L"# ignoring recursive shebang command\n"); 821 return RC_RECURSIVE_SHEBANG; 822 } 823 824 wchar_t *buf = allocSearchInfoBuffer(search, n + 1); 825 if (!buf || wcscpy_s(buf, n + 1, buffer)) { 826 return RC_NO_MEMORY; 827 } 828 829 search->executablePath = buf; 830 search->executableArgs = &command[commandLength]; 831 search->executableArgsLength = shebangLength - commandLength; 832 debug(L"# Found %s on PATH\n", buf); 833 834 return 0; 835} 836 837 838int 839_readIni(const wchar_t *section, const wchar_t *settingName, wchar_t *buffer, int bufferLength) 840{ 841 wchar_t iniPath[MAXLEN]; 842 int n; 843 if (SUCCEEDED(SHGetFolderPathW(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, iniPath)) && 844 join(iniPath, MAXLEN, L"py.ini")) { 845 debug(L"# Reading from %s for %s/%s\n", iniPath, section, settingName); 846 n = GetPrivateProfileStringW(section, settingName, NULL, buffer, bufferLength, iniPath); 847 if (n) { 848 debug(L"# Found %s in %s\n", settingName, iniPath); 849 return n; 850 } else if (GetLastError() == ERROR_FILE_NOT_FOUND) { 851 debug(L"# Did not find file %s\n", iniPath); 852 } else { 853 winerror(0, L"Failed to read from %s\n", iniPath); 854 } 855 } 856 if (GetModuleFileNameW(NULL, iniPath, MAXLEN) && 857 SUCCEEDED(PathCchRemoveFileSpec(iniPath, MAXLEN)) && 858 join(iniPath, MAXLEN, L"py.ini")) { 859 debug(L"# Reading from %s for %s/%s\n", iniPath, section, settingName); 860 n = GetPrivateProfileStringW(section, settingName, NULL, buffer, MAXLEN, iniPath); 861 if (n) { 862 debug(L"# Found %s in %s\n", settingName, iniPath); 863 return n; 864 } else if (GetLastError() == ERROR_FILE_NOT_FOUND) { 865 debug(L"# Did not find file %s\n", iniPath); 866 } else { 867 winerror(0, L"Failed to read from %s\n", iniPath); 868 } 869 } 870 return 0; 871} 872 873 874bool 875_findCommand(SearchInfo *search, const wchar_t *command, int commandLength) 876{ 877 wchar_t commandBuffer[MAXLEN]; 878 wchar_t buffer[MAXLEN]; 879 wcsncpy_s(commandBuffer, MAXLEN, command, commandLength); 880 int n = _readIni(L"commands", commandBuffer, buffer, MAXLEN); 881 if (!n) { 882 return false; 883 } 884 wchar_t *path = allocSearchInfoBuffer(search, n + 1); 885 if (!path) { 886 return false; 887 } 888 wcscpy_s(path, n + 1, buffer); 889 search->executablePath = path; 890 return true; 891} 892 893 894int 895_useShebangAsExecutable(SearchInfo *search, const wchar_t *shebang, int shebangLength) 896{ 897 wchar_t buffer[MAXLEN]; 898 wchar_t script[MAXLEN]; 899 wchar_t command[MAXLEN]; 900 901 int commandLength = 0; 902 int inQuote = 0; 903 904 if (!shebang || !shebangLength) { 905 return 0; 906 } 907 908 wchar_t *pC = command; 909 for (int i = 0; i < shebangLength; ++i) { 910 wchar_t c = shebang[i]; 911 if (isspace(c) && !inQuote) { 912 commandLength = i; 913 break; 914 } else if (c == L'"') { 915 inQuote = !inQuote; 916 } else if (c == L'/' || c == L'\\') { 917 *pC++ = L'\\'; 918 } else { 919 *pC++ = c; 920 } 921 } 922 *pC = L'\0'; 923 924 if (!GetCurrentDirectoryW(MAXLEN, buffer) || 925 wcsncpy_s(script, MAXLEN, search->scriptFile, search->scriptFileLength) || 926 FAILED(PathCchCombineEx(buffer, MAXLEN, buffer, script, 927 PATHCCH_ALLOW_LONG_PATHS)) || 928 FAILED(PathCchRemoveFileSpec(buffer, MAXLEN)) || 929 FAILED(PathCchCombineEx(buffer, MAXLEN, buffer, command, 930 PATHCCH_ALLOW_LONG_PATHS)) 931 ) { 932 return RC_NO_MEMORY; 933 } 934 935 int n = (int)wcsnlen(buffer, MAXLEN); 936 wchar_t *path = allocSearchInfoBuffer(search, n + 1); 937 if (!path) { 938 return RC_NO_MEMORY; 939 } 940 wcscpy_s(path, n + 1, buffer); 941 search->executablePath = path; 942 if (commandLength) { 943 search->executableArgs = &shebang[commandLength]; 944 search->executableArgsLength = shebangLength - commandLength; 945 } 946 return 0; 947} 948 949 950int 951checkShebang(SearchInfo *search) 952{ 953 // Do not check shebang if a tag was provided or if no script file 954 // was found on the command line. 955 if (search->tag || !search->scriptFile) { 956 return 0; 957 } 958 959 if (search->scriptFileLength < 0) { 960 search->scriptFileLength = (int)wcsnlen_s(search->scriptFile, MAXLEN); 961 } 962 963 wchar_t *scriptFile = (wchar_t*)malloc(sizeof(wchar_t) * (search->scriptFileLength + 1)); 964 if (!scriptFile) { 965 return RC_NO_MEMORY; 966 } 967 968 wcsncpy_s(scriptFile, search->scriptFileLength + 1, 969 search->scriptFile, search->scriptFileLength); 970 971 HANDLE hFile = CreateFileW(scriptFile, GENERIC_READ, 972 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 973 NULL, OPEN_EXISTING, 0, NULL); 974 975 if (hFile == INVALID_HANDLE_VALUE) { 976 debug(L"# Failed to open %s for shebang parsing (0x%08X)\n", 977 scriptFile, GetLastError()); 978 free(scriptFile); 979 return 0; 980 } 981 982 DWORD bytesRead = 0; 983 char buffer[4096]; 984 if (!ReadFile(hFile, buffer, sizeof(buffer), &bytesRead, NULL)) { 985 debug(L"# Failed to read %s for shebang parsing (0x%08X)\n", 986 scriptFile, GetLastError()); 987 free(scriptFile); 988 return 0; 989 } 990 991 CloseHandle(hFile); 992 debug(L"# Read %d bytes from %s to find shebang line\n", bytesRead, scriptFile); 993 free(scriptFile); 994 995 996 char *b = buffer; 997 bool onlyUtf8 = false; 998 if (bytesRead > 3 && *b == 0xEF) { 999 if (*++b == 0xBB && *++b == 0xBF) { 1000 // Allow a UTF-8 BOM 1001 ++b; 1002 bytesRead -= 3; 1003 onlyUtf8 = true; 1004 } else { 1005 debug(L"# Invalid BOM in shebang line"); 1006 return 0; 1007 } 1008 } 1009 if (bytesRead <= 2 || b[0] != '#' || b[1] != '!') { 1010 // No shebang (#!) at start of line 1011 debug(L"# No valid shebang line"); 1012 return 0; 1013 } 1014 ++b; 1015 --bytesRead; 1016 while (--bytesRead > 0 && isspace(*++b)) { } 1017 char *start = b; 1018 while (--bytesRead > 0 && *++b != '\r' && *b != '\n') { } 1019 wchar_t *shebang; 1020 int shebangLength; 1021 // We add 1 when bytesRead==0, as in that case we hit EOF and b points 1022 // to the last character in the file, not the newline 1023 int exitCode = _decodeShebang(search, start, (int)(b - start + (bytesRead == 0)), onlyUtf8, &shebang, &shebangLength); 1024 if (exitCode) { 1025 return exitCode; 1026 } 1027 debug(L"Shebang: %s\n", shebang); 1028 1029 // Handle shebangs that we should search PATH for 1030 exitCode = searchPath(search, shebang, shebangLength); 1031 if (exitCode != RC_NO_SHEBANG) { 1032 return exitCode; 1033 } 1034 1035 // Handle some known, case-sensitive shebangs 1036 const wchar_t *command; 1037 int commandLength; 1038 // Each template must end with "python" 1039 static const wchar_t *shebangTemplates[] = { 1040 L"/usr/bin/env python", 1041 L"/usr/bin/python", 1042 L"/usr/local/bin/python", 1043 L"python", 1044 NULL 1045 }; 1046 1047 for (const wchar_t **tmpl = shebangTemplates; *tmpl; ++tmpl) { 1048 // Just to make sure we don't mess this up in the future 1049 assert(0 == wcscmp(L"python", (*tmpl) + wcslen(*tmpl) - 6)); 1050 1051 if (_shebangStartsWith(shebang, shebangLength, *tmpl, &command, &commandLength)) { 1052 // Search for "python{command}" overrides. All templates end with 1053 // "python", so we prepend it by jumping back 6 characters 1054 if (_findCommand(search, &command[-6], commandLength + 6)) { 1055 search->executableArgs = &command[commandLength]; 1056 search->executableArgsLength = shebangLength - commandLength; 1057 debug(L"# Treating shebang command '%.*s' as %s\n", 1058 commandLength + 6, &command[-6], search->executablePath); 1059 return 0; 1060 } 1061 1062 search->tag = command; 1063 search->tagLength = commandLength; 1064 // If we had 'python3.12.exe' then we want to strip the suffix 1065 // off of the tag 1066 if (search->tagLength > 4) { 1067 const wchar_t *suffix = &search->tag[search->tagLength - 4]; 1068 if (0 == _comparePath(suffix, 4, L".exe", -1)) { 1069 search->tagLength -= 4; 1070 } 1071 } 1072 // If we had 'python3_d' then we want to strip the '_d' (any 1073 // '.exe' is already gone) 1074 if (search->tagLength > 2) { 1075 const wchar_t *suffix = &search->tag[search->tagLength - 2]; 1076 if (0 == _comparePath(suffix, 2, L"_d", -1)) { 1077 search->tagLength -= 2; 1078 } 1079 } 1080 search->oldStyleTag = true; 1081 search->executableArgs = &command[commandLength]; 1082 search->executableArgsLength = shebangLength - commandLength; 1083 if (search->tag && search->tagLength) { 1084 debug(L"# Treating shebang command '%.*s' as 'py -%.*s'\n", 1085 commandLength, command, search->tagLength, search->tag); 1086 } else { 1087 debug(L"# Treating shebang command '%.*s' as 'py'\n", 1088 commandLength, command); 1089 } 1090 return 0; 1091 } 1092 } 1093 1094 // Unrecognised executables are first tried as command aliases 1095 commandLength = 0; 1096 while (commandLength < shebangLength && !isspace(shebang[commandLength])) { 1097 commandLength += 1; 1098 } 1099 if (_findCommand(search, shebang, commandLength)) { 1100 search->executableArgs = &shebang[commandLength]; 1101 search->executableArgsLength = shebangLength - commandLength; 1102 debug(L"# Treating shebang command '%.*s' as %s\n", 1103 commandLength, shebang, search->executablePath); 1104 return 0; 1105 } 1106 1107 // Unrecognised commands are joined to the script's directory and treated 1108 // as the executable path 1109 return _useShebangAsExecutable(search, shebang, shebangLength); 1110} 1111 1112 1113int 1114checkDefaults(SearchInfo *search) 1115{ 1116 if (!search->allowDefaults) { 1117 return 0; 1118 } 1119 1120 // Only resolve old-style (or absent) tags to defaults 1121 if (search->tag && search->tagLength && !search->oldStyleTag) { 1122 return 0; 1123 } 1124 1125 // If tag is only a major version number, expand it from the environment 1126 // or an ini file 1127 const wchar_t *iniSettingName = NULL; 1128 const wchar_t *envSettingName = NULL; 1129 if (!search->tag || !search->tagLength) { 1130 iniSettingName = L"python"; 1131 envSettingName = L"py_python"; 1132 } else if (0 == wcsncmp(search->tag, L"3", search->tagLength)) { 1133 iniSettingName = L"python3"; 1134 envSettingName = L"py_python3"; 1135 } else if (0 == wcsncmp(search->tag, L"2", search->tagLength)) { 1136 iniSettingName = L"python2"; 1137 envSettingName = L"py_python2"; 1138 } else { 1139 debug(L"# Cannot select defaults for tag '%.*s'\n", search->tagLength, search->tag); 1140 return 0; 1141 } 1142 1143 // First, try to read an environment variable 1144 wchar_t buffer[MAXLEN]; 1145 int n = GetEnvironmentVariableW(envSettingName, buffer, MAXLEN); 1146 1147 // If none found, check in our two .ini files instead 1148 if (!n) { 1149 n = _readIni(L"defaults", iniSettingName, buffer, MAXLEN); 1150 } 1151 1152 if (n) { 1153 wchar_t *tag = allocSearchInfoBuffer(search, n + 1); 1154 if (!tag) { 1155 return RC_NO_MEMORY; 1156 } 1157 wcscpy_s(tag, n + 1, buffer); 1158 wchar_t *slash = wcschr(tag, L'/'); 1159 if (!slash) { 1160 search->tag = tag; 1161 search->tagLength = n; 1162 search->oldStyleTag = true; 1163 } else { 1164 search->company = tag; 1165 search->companyLength = (int)(slash - tag); 1166 search->tag = slash + 1; 1167 search->tagLength = n - (search->companyLength + 1); 1168 search->oldStyleTag = false; 1169 } 1170 // gh-92817: allow a high priority env to be selected even if it 1171 // doesn't match the tag 1172 search->lowPriorityTag = true; 1173 } 1174 1175 return 0; 1176} 1177 1178/******************************************************************************\ 1179 *** ENVIRONMENT SEARCH *** 1180\******************************************************************************/ 1181 1182typedef struct EnvironmentInfo { 1183 /* We use a binary tree and sort on insert */ 1184 struct EnvironmentInfo *prev; 1185 struct EnvironmentInfo *next; 1186 /* parent is only used when constructing */ 1187 struct EnvironmentInfo *parent; 1188 const wchar_t *company; 1189 const wchar_t *tag; 1190 int internalSortKey; 1191 const wchar_t *installDir; 1192 const wchar_t *executablePath; 1193 const wchar_t *executableArgs; 1194 const wchar_t *architecture; 1195 const wchar_t *displayName; 1196 bool highPriority; 1197} EnvironmentInfo; 1198 1199 1200int 1201copyWstr(const wchar_t **dest, const wchar_t *src) 1202{ 1203 if (!dest) { 1204 return RC_NO_MEMORY; 1205 } 1206 if (!src) { 1207 *dest = NULL; 1208 return 0; 1209 } 1210 size_t n = wcsnlen_s(src, MAXLEN - 1) + 1; 1211 wchar_t *buffer = (wchar_t*)malloc(n * sizeof(wchar_t)); 1212 if (!buffer) { 1213 return RC_NO_MEMORY; 1214 } 1215 wcsncpy_s(buffer, n, src, n - 1); 1216 *dest = (const wchar_t*)buffer; 1217 return 0; 1218} 1219 1220 1221EnvironmentInfo * 1222newEnvironmentInfo(const wchar_t *company, const wchar_t *tag) 1223{ 1224 EnvironmentInfo *env = (EnvironmentInfo *)malloc(sizeof(EnvironmentInfo)); 1225 if (!env) { 1226 return NULL; 1227 } 1228 memset(env, 0, sizeof(EnvironmentInfo)); 1229 int exitCode = copyWstr(&env->company, company); 1230 if (exitCode) { 1231 free((void *)env); 1232 return NULL; 1233 } 1234 exitCode = copyWstr(&env->tag, tag); 1235 if (exitCode) { 1236 free((void *)env->company); 1237 free((void *)env); 1238 return NULL; 1239 } 1240 return env; 1241} 1242 1243 1244void 1245freeEnvironmentInfo(EnvironmentInfo *env) 1246{ 1247 if (env) { 1248 free((void *)env->company); 1249 free((void *)env->tag); 1250 free((void *)env->installDir); 1251 free((void *)env->executablePath); 1252 free((void *)env->executableArgs); 1253 free((void *)env->displayName); 1254 freeEnvironmentInfo(env->prev); 1255 env->prev = NULL; 1256 freeEnvironmentInfo(env->next); 1257 env->next = NULL; 1258 free((void *)env); 1259 } 1260} 1261 1262 1263/* Specific string comparisons for sorting the tree */ 1264 1265int 1266_compareCompany(const wchar_t *x, const wchar_t *y) 1267{ 1268 if (!x && !y) { 1269 return 0; 1270 } else if (!x) { 1271 return -1; 1272 } else if (!y) { 1273 return 1; 1274 } 1275 1276 bool coreX = 0 == _compare(x, -1, L"PythonCore", -1); 1277 bool coreY = 0 == _compare(y, -1, L"PythonCore", -1); 1278 if (coreX) { 1279 return coreY ? 0 : -1; 1280 } else if (coreY) { 1281 return 1; 1282 } 1283 return _compare(x, -1, y, -1); 1284} 1285 1286 1287int 1288_compareTag(const wchar_t *x, const wchar_t *y) 1289{ 1290 if (!x && !y) { 1291 return 0; 1292 } else if (!x) { 1293 return -1; 1294 } else if (!y) { 1295 return 1; 1296 } 1297 1298 // Compare up to the first dash. If not equal, that's our sort order 1299 const wchar_t *xDash = wcschr(x, L'-'); 1300 const wchar_t *yDash = wcschr(y, L'-'); 1301 int xToDash = xDash ? (int)(xDash - x) : -1; 1302 int yToDash = yDash ? (int)(yDash - y) : -1; 1303 int r = _compare(x, xToDash, y, yToDash); 1304 if (r) { 1305 return r; 1306 } 1307 // If we're equal up to the first dash, we want to sort one with 1308 // no dash *after* one with a dash. Otherwise, a reversed compare. 1309 // This works out because environments are sorted in descending tag 1310 // order, so that higher versions (probably) come first. 1311 // For PythonCore, our "X.Y" structure ensures that higher versions 1312 // come first. Everyone else will just have to deal with it. 1313 if (xDash && yDash) { 1314 return _compare(yDash, -1, xDash, -1); 1315 } else if (xDash) { 1316 return -1; 1317 } else if (yDash) { 1318 return 1; 1319 } 1320 return 0; 1321} 1322 1323 1324int 1325addEnvironmentInfo(EnvironmentInfo **root, EnvironmentInfo* parent, EnvironmentInfo *node) 1326{ 1327 EnvironmentInfo *r = *root; 1328 if (!r) { 1329 *root = node; 1330 node->parent = parent; 1331 return 0; 1332 } 1333 // Sort by company name 1334 switch (_compareCompany(node->company, r->company)) { 1335 case -1: 1336 return addEnvironmentInfo(&r->prev, r, node); 1337 case 1: 1338 return addEnvironmentInfo(&r->next, r, node); 1339 case 0: 1340 break; 1341 } 1342 // Then by tag (descending) 1343 switch (_compareTag(node->tag, r->tag)) { 1344 case -1: 1345 return addEnvironmentInfo(&r->next, r, node); 1346 case 1: 1347 return addEnvironmentInfo(&r->prev, r, node); 1348 case 0: 1349 break; 1350 } 1351 // Then keep the one with the lowest internal sort key 1352 if (node->internalSortKey < r->internalSortKey) { 1353 // Replace the current node 1354 node->parent = r->parent; 1355 if (node->parent) { 1356 if (node->parent->prev == r) { 1357 node->parent->prev = node; 1358 } else if (node->parent->next == r) { 1359 node->parent->next = node; 1360 } else { 1361 debug(L"# Inconsistent parent value in tree\n"); 1362 freeEnvironmentInfo(node); 1363 return RC_INTERNAL_ERROR; 1364 } 1365 } else { 1366 // If node has no parent, then it is the root. 1367 *root = node; 1368 } 1369 1370 node->next = r->next; 1371 node->prev = r->prev; 1372 1373 debug(L"# replaced %s/%s/%i in tree\n", node->company, node->tag, node->internalSortKey); 1374 freeEnvironmentInfo(r); 1375 } else { 1376 debug(L"# not adding %s/%s/%i to tree\n", node->company, node->tag, node->internalSortKey); 1377 return RC_DUPLICATE_ITEM; 1378 } 1379 return 0; 1380} 1381 1382 1383/******************************************************************************\ 1384 *** REGISTRY SEARCH *** 1385\******************************************************************************/ 1386 1387 1388int 1389_registryReadString(const wchar_t **dest, HKEY root, const wchar_t *subkey, const wchar_t *value) 1390{ 1391 // Note that this is bytes (hence 'cb'), not characters ('cch') 1392 DWORD cbData = 0; 1393 DWORD flags = RRF_RT_REG_SZ | RRF_RT_REG_EXPAND_SZ; 1394 1395 if (ERROR_SUCCESS != RegGetValueW(root, subkey, value, flags, NULL, NULL, &cbData)) { 1396 return 0; 1397 } 1398 1399 wchar_t *buffer = (wchar_t*)malloc(cbData); 1400 if (!buffer) { 1401 return RC_NO_MEMORY; 1402 } 1403 1404 if (ERROR_SUCCESS == RegGetValueW(root, subkey, value, flags, NULL, buffer, &cbData)) { 1405 *dest = buffer; 1406 } else { 1407 free((void *)buffer); 1408 } 1409 return 0; 1410} 1411 1412 1413int 1414_combineWithInstallDir(const wchar_t **dest, const wchar_t *installDir, const wchar_t *fragment, int fragmentLength) 1415{ 1416 wchar_t buffer[MAXLEN]; 1417 wchar_t fragmentBuffer[MAXLEN]; 1418 if (wcsncpy_s(fragmentBuffer, MAXLEN, fragment, fragmentLength)) { 1419 return RC_NO_MEMORY; 1420 } 1421 1422 if (FAILED(PathCchCombineEx(buffer, MAXLEN, installDir, fragmentBuffer, PATHCCH_ALLOW_LONG_PATHS))) { 1423 return RC_NO_MEMORY; 1424 } 1425 1426 return copyWstr(dest, buffer); 1427} 1428 1429 1430bool 1431_isLegacyVersion(EnvironmentInfo *env) 1432{ 1433 // Check if backwards-compatibility is required. 1434 // Specifically PythonCore versions 2.X and 3.0 - 3.5 do not implement PEP 514. 1435 if (0 != _compare(env->company, -1, L"PythonCore", -1)) { 1436 return false; 1437 } 1438 1439 int versionMajor, versionMinor; 1440 int n = swscanf_s(env->tag, L"%d.%d", &versionMajor, &versionMinor); 1441 if (n != 2) { 1442 debug(L"# %s/%s has an invalid version tag\n", env->company, env->tag); 1443 return false; 1444 } 1445 1446 return versionMajor == 2 1447 || (versionMajor == 3 && versionMinor >= 0 && versionMinor <= 5); 1448} 1449 1450int 1451_registryReadLegacyEnvironment(const SearchInfo *search, HKEY root, EnvironmentInfo *env, const wchar_t *fallbackArch) 1452{ 1453 // Backwards-compatibility for PythonCore versions which do not implement PEP 514. 1454 int exitCode = _combineWithInstallDir( 1455 &env->executablePath, 1456 env->installDir, 1457 search->executable, 1458 search->executableLength 1459 ); 1460 if (exitCode) { 1461 return exitCode; 1462 } 1463 1464 if (search->windowed) { 1465 exitCode = _registryReadString(&env->executableArgs, root, L"InstallPath", L"WindowedExecutableArguments"); 1466 } 1467 else { 1468 exitCode = _registryReadString(&env->executableArgs, root, L"InstallPath", L"ExecutableArguments"); 1469 } 1470 if (exitCode) { 1471 return exitCode; 1472 } 1473 1474 if (fallbackArch) { 1475 copyWstr(&env->architecture, fallbackArch); 1476 } else { 1477 DWORD binaryType; 1478 BOOL success = GetBinaryTypeW(env->executablePath, &binaryType); 1479 if (!success) { 1480 return RC_NO_PYTHON; 1481 } 1482 1483 switch (binaryType) { 1484 case SCS_32BIT_BINARY: 1485 copyWstr(&env->architecture, L"32bit"); 1486 break; 1487 case SCS_64BIT_BINARY: 1488 copyWstr(&env->architecture, L"64bit"); 1489 break; 1490 default: 1491 return RC_NO_PYTHON; 1492 } 1493 } 1494 1495 if (0 == _compare(env->architecture, -1, L"32bit", -1)) { 1496 size_t tagLength = wcslen(env->tag); 1497 if (tagLength <= 3 || 0 != _compare(&env->tag[tagLength - 3], 3, L"-32", 3)) { 1498 const wchar_t *rawTag = env->tag; 1499 wchar_t *realTag = (wchar_t*) malloc(sizeof(wchar_t) * (tagLength + 4)); 1500 if (!realTag) { 1501 return RC_NO_MEMORY; 1502 } 1503 1504 int count = swprintf_s(realTag, tagLength + 4, L"%s-32", env->tag); 1505 if (count == -1) { 1506 free(realTag); 1507 return RC_INTERNAL_ERROR; 1508 } 1509 1510 env->tag = realTag; 1511 free((void*)rawTag); 1512 } 1513 } 1514 1515 wchar_t buffer[MAXLEN]; 1516 if (swprintf_s(buffer, MAXLEN, L"Python %s", env->tag)) { 1517 copyWstr(&env->displayName, buffer); 1518 } 1519 1520 return 0; 1521} 1522 1523 1524int 1525_registryReadEnvironment(const SearchInfo *search, HKEY root, EnvironmentInfo *env, const wchar_t *fallbackArch) 1526{ 1527 int exitCode = _registryReadString(&env->installDir, root, L"InstallPath", NULL); 1528 if (exitCode) { 1529 return exitCode; 1530 } 1531 if (!env->installDir) { 1532 return RC_NO_PYTHON; 1533 } 1534 1535 if (_isLegacyVersion(env)) { 1536 return _registryReadLegacyEnvironment(search, root, env, fallbackArch); 1537 } 1538 1539 // If pythonw.exe requested, check specific value 1540 if (search->windowed) { 1541 exitCode = _registryReadString(&env->executablePath, root, L"InstallPath", L"WindowedExecutablePath"); 1542 if (!exitCode && env->executablePath) { 1543 exitCode = _registryReadString(&env->executableArgs, root, L"InstallPath", L"WindowedExecutableArguments"); 1544 } 1545 } 1546 if (exitCode) { 1547 return exitCode; 1548 } 1549 1550 // Missing windowed path or non-windowed request means we use ExecutablePath 1551 if (!env->executablePath) { 1552 exitCode = _registryReadString(&env->executablePath, root, L"InstallPath", L"ExecutablePath"); 1553 if (!exitCode && env->executablePath) { 1554 exitCode = _registryReadString(&env->executableArgs, root, L"InstallPath", L"ExecutableArguments"); 1555 } 1556 } 1557 if (exitCode) { 1558 return exitCode; 1559 } 1560 1561 if (!env->executablePath) { 1562 debug(L"# %s/%s has no executable path\n", env->company, env->tag); 1563 return RC_NO_PYTHON; 1564 } 1565 1566 exitCode = _registryReadString(&env->architecture, root, NULL, L"SysArchitecture"); 1567 if (exitCode) { 1568 return exitCode; 1569 } 1570 1571 exitCode = _registryReadString(&env->displayName, root, NULL, L"DisplayName"); 1572 if (exitCode) { 1573 return exitCode; 1574 } 1575 1576 return 0; 1577} 1578 1579int 1580_registrySearchTags(const SearchInfo *search, EnvironmentInfo **result, HKEY root, int sortKey, const wchar_t *company, const wchar_t *fallbackArch) 1581{ 1582 wchar_t buffer[256]; 1583 int err = 0; 1584 int exitCode = 0; 1585 for (int i = 0; exitCode == 0; ++i) { 1586 DWORD cchBuffer = sizeof(buffer) / sizeof(buffer[0]); 1587 err = RegEnumKeyExW(root, i, buffer, &cchBuffer, NULL, NULL, NULL, NULL); 1588 if (err) { 1589 if (err != ERROR_NO_MORE_ITEMS) { 1590 winerror(0, L"Failed to read installs (tags) from the registry"); 1591 } 1592 break; 1593 } 1594 HKEY subkey; 1595 if (ERROR_SUCCESS == RegOpenKeyExW(root, buffer, 0, KEY_READ, &subkey)) { 1596 EnvironmentInfo *env = newEnvironmentInfo(company, buffer); 1597 env->internalSortKey = sortKey; 1598 exitCode = _registryReadEnvironment(search, subkey, env, fallbackArch); 1599 RegCloseKey(subkey); 1600 if (exitCode == RC_NO_PYTHON) { 1601 freeEnvironmentInfo(env); 1602 exitCode = 0; 1603 } else if (!exitCode) { 1604 exitCode = addEnvironmentInfo(result, NULL, env); 1605 if (exitCode) { 1606 freeEnvironmentInfo(env); 1607 if (exitCode == RC_DUPLICATE_ITEM) { 1608 exitCode = 0; 1609 } 1610 } 1611 } 1612 } 1613 } 1614 return exitCode; 1615} 1616 1617 1618int 1619registrySearch(const SearchInfo *search, EnvironmentInfo **result, HKEY root, int sortKey, const wchar_t *fallbackArch) 1620{ 1621 wchar_t buffer[256]; 1622 int err = 0; 1623 int exitCode = 0; 1624 for (int i = 0; exitCode == 0; ++i) { 1625 DWORD cchBuffer = sizeof(buffer) / sizeof(buffer[0]); 1626 err = RegEnumKeyExW(root, i, buffer, &cchBuffer, NULL, NULL, NULL, NULL); 1627 if (err) { 1628 if (err != ERROR_NO_MORE_ITEMS) { 1629 winerror(0, L"Failed to read distributors (company) from the registry"); 1630 } 1631 break; 1632 } 1633 if (search->limitToCompany && 0 != _compare(search->limitToCompany, -1, buffer, cchBuffer)) { 1634 debug(L"# Skipping %s due to PYLAUNCHER_LIMIT_TO_COMPANY\n", buffer); 1635 continue; 1636 } 1637 HKEY subkey; 1638 if (ERROR_SUCCESS == RegOpenKeyExW(root, buffer, 0, KEY_READ, &subkey)) { 1639 exitCode = _registrySearchTags(search, result, subkey, sortKey, buffer, fallbackArch); 1640 RegCloseKey(subkey); 1641 } 1642 } 1643 return exitCode; 1644} 1645 1646 1647/******************************************************************************\ 1648 *** APP PACKAGE SEARCH *** 1649\******************************************************************************/ 1650 1651int 1652appxSearch(const SearchInfo *search, EnvironmentInfo **result, const wchar_t *packageFamilyName, const wchar_t *tag, int sortKey) 1653{ 1654 wchar_t realTag[32]; 1655 wchar_t buffer[MAXLEN]; 1656 const wchar_t *exeName = search->executable; 1657 if (!exeName || search->allowExecutableOverride) { 1658 exeName = search->windowed ? L"pythonw.exe" : L"python.exe"; 1659 } 1660 1661 if (FAILED(SHGetFolderPathW(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, buffer)) || 1662 !join(buffer, MAXLEN, L"Microsoft\\WindowsApps") || 1663 !join(buffer, MAXLEN, packageFamilyName) || 1664 !join(buffer, MAXLEN, exeName)) { 1665 return RC_INTERNAL_ERROR; 1666 } 1667 1668 if (INVALID_FILE_ATTRIBUTES == GetFileAttributesW(buffer)) { 1669 return RC_NO_PYTHON; 1670 } 1671 1672 // Assume packages are native architecture, which means we need to append 1673 // the '-arm64' on ARM64 host. 1674 wcscpy_s(realTag, 32, tag); 1675 if (isARM64Host()) { 1676 wcscat_s(realTag, 32, L"-arm64"); 1677 } 1678 1679 EnvironmentInfo *env = newEnvironmentInfo(L"PythonCore", realTag); 1680 if (!env) { 1681 return RC_NO_MEMORY; 1682 } 1683 env->internalSortKey = sortKey; 1684 if (isAMD64Host()) { 1685 copyWstr(&env->architecture, L"64bit"); 1686 } else if (isARM64Host()) { 1687 copyWstr(&env->architecture, L"ARM64"); 1688 } 1689 1690 copyWstr(&env->executablePath, buffer); 1691 1692 if (swprintf_s(buffer, MAXLEN, L"Python %s (Store)", tag)) { 1693 copyWstr(&env->displayName, buffer); 1694 } 1695 1696 int exitCode = addEnvironmentInfo(result, NULL, env); 1697 if (exitCode) { 1698 freeEnvironmentInfo(env); 1699 if (exitCode == RC_DUPLICATE_ITEM) { 1700 exitCode = 0; 1701 } 1702 } 1703 1704 1705 return exitCode; 1706} 1707 1708 1709/******************************************************************************\ 1710 *** OVERRIDDEN EXECUTABLE PATH *** 1711\******************************************************************************/ 1712 1713 1714int 1715explicitOverrideSearch(const SearchInfo *search, EnvironmentInfo **result) 1716{ 1717 if (!search->executablePath) { 1718 return 0; 1719 } 1720 1721 EnvironmentInfo *env = newEnvironmentInfo(NULL, NULL); 1722 if (!env) { 1723 return RC_NO_MEMORY; 1724 } 1725 env->internalSortKey = 10; 1726 int exitCode = copyWstr(&env->executablePath, search->executablePath); 1727 if (exitCode) { 1728 goto abort; 1729 } 1730 exitCode = copyWstr(&env->displayName, L"Explicit override"); 1731 if (exitCode) { 1732 goto abort; 1733 } 1734 exitCode = addEnvironmentInfo(result, NULL, env); 1735 if (exitCode) { 1736 goto abort; 1737 } 1738 return 0; 1739 1740abort: 1741 freeEnvironmentInfo(env); 1742 if (exitCode == RC_DUPLICATE_ITEM) { 1743 exitCode = 0; 1744 } 1745 return exitCode; 1746} 1747 1748 1749/******************************************************************************\ 1750 *** ACTIVE VIRTUAL ENVIRONMENT SEARCH *** 1751\******************************************************************************/ 1752 1753int 1754virtualenvSearch(const SearchInfo *search, EnvironmentInfo **result) 1755{ 1756 int exitCode = 0; 1757 EnvironmentInfo *env = NULL; 1758 wchar_t buffer[MAXLEN]; 1759 int n = GetEnvironmentVariableW(L"VIRTUAL_ENV", buffer, MAXLEN); 1760 if (!n || !join(buffer, MAXLEN, L"Scripts") || !join(buffer, MAXLEN, search->executable)) { 1761 return 0; 1762 } 1763 1764 if (INVALID_FILE_ATTRIBUTES == GetFileAttributesW(buffer)) { 1765 debug(L"Python executable %s missing from virtual env\n", buffer); 1766 return 0; 1767 } 1768 1769 env = newEnvironmentInfo(NULL, NULL); 1770 if (!env) { 1771 return RC_NO_MEMORY; 1772 } 1773 env->highPriority = true; 1774 env->internalSortKey = 20; 1775 exitCode = copyWstr(&env->displayName, L"Active venv"); 1776 if (exitCode) { 1777 goto abort; 1778 } 1779 exitCode = copyWstr(&env->executablePath, buffer); 1780 if (exitCode) { 1781 goto abort; 1782 } 1783 exitCode = addEnvironmentInfo(result, NULL, env); 1784 if (exitCode) { 1785 goto abort; 1786 } 1787 return 0; 1788 1789abort: 1790 freeEnvironmentInfo(env); 1791 if (exitCode == RC_DUPLICATE_ITEM) { 1792 return 0; 1793 } 1794 return exitCode; 1795} 1796 1797/******************************************************************************\ 1798 *** COLLECT ENVIRONMENTS *** 1799\******************************************************************************/ 1800 1801 1802struct RegistrySearchInfo { 1803 // Registry subkey to search 1804 const wchar_t *subkey; 1805 // Registry hive to search 1806 HKEY hive; 1807 // Flags to use when opening the subkey 1808 DWORD flags; 1809 // Internal sort key to select between "identical" environments discovered 1810 // through different methods 1811 int sortKey; 1812 // Fallback value to assume for PythonCore entries missing a SysArchitecture value 1813 const wchar_t *fallbackArch; 1814}; 1815 1816 1817struct RegistrySearchInfo REGISTRY_SEARCH[] = { 1818 { 1819 L"Software\\Python", 1820 HKEY_CURRENT_USER, 1821 KEY_READ, 1822 1, 1823 NULL 1824 }, 1825 { 1826 L"Software\\Python", 1827 HKEY_LOCAL_MACHINE, 1828 KEY_READ | KEY_WOW64_64KEY, 1829 3, 1830 L"64bit" 1831 }, 1832 { 1833 L"Software\\Python", 1834 HKEY_LOCAL_MACHINE, 1835 KEY_READ | KEY_WOW64_32KEY, 1836 4, 1837 L"32bit" 1838 }, 1839 { NULL, 0, 0, 0, NULL } 1840}; 1841 1842 1843struct AppxSearchInfo { 1844 // The package family name. Can be found for an installed package using the 1845 // Powershell "Get-AppxPackage" cmdlet 1846 const wchar_t *familyName; 1847 // The tag to treat the installation as 1848 const wchar_t *tag; 1849 // Internal sort key to select between "identical" environments discovered 1850 // through different methods 1851 int sortKey; 1852}; 1853 1854 1855struct AppxSearchInfo APPX_SEARCH[] = { 1856 // Releases made through the Store 1857 { L"PythonSoftwareFoundation.Python.3.12_qbz5n2kfra8p0", L"3.12", 10 }, 1858 { L"PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0", L"3.11", 10 }, 1859 { L"PythonSoftwareFoundation.Python.3.10_qbz5n2kfra8p0", L"3.10", 10 }, 1860 { L"PythonSoftwareFoundation.Python.3.9_qbz5n2kfra8p0", L"3.9", 10 }, 1861 { L"PythonSoftwareFoundation.Python.3.8_qbz5n2kfra8p0", L"3.8", 10 }, 1862 1863 // Side-loadable releases. Note that the publisher ID changes whenever we 1864 // renew our code-signing certificate, so the newer ID has a higher 1865 // priority (lower sortKey) 1866 { L"PythonSoftwareFoundation.Python.3.12_3847v3x7pw1km", L"3.12", 11 }, 1867 { L"PythonSoftwareFoundation.Python.3.11_3847v3x7pw1km", L"3.11", 11 }, 1868 { L"PythonSoftwareFoundation.Python.3.11_hd69rhyc2wevp", L"3.11", 12 }, 1869 { L"PythonSoftwareFoundation.Python.3.10_3847v3x7pw1km", L"3.10", 11 }, 1870 { L"PythonSoftwareFoundation.Python.3.10_hd69rhyc2wevp", L"3.10", 12 }, 1871 { L"PythonSoftwareFoundation.Python.3.9_3847v3x7pw1km", L"3.9", 11 }, 1872 { L"PythonSoftwareFoundation.Python.3.9_hd69rhyc2wevp", L"3.9", 12 }, 1873 { L"PythonSoftwareFoundation.Python.3.8_hd69rhyc2wevp", L"3.8", 12 }, 1874 { NULL, NULL, 0 } 1875}; 1876 1877 1878int 1879collectEnvironments(const SearchInfo *search, EnvironmentInfo **result) 1880{ 1881 int exitCode = 0; 1882 HKEY root; 1883 EnvironmentInfo *env = NULL; 1884 1885 if (!result) { 1886 return RC_INTERNAL_ERROR; 1887 } 1888 *result = NULL; 1889 1890 exitCode = explicitOverrideSearch(search, result); 1891 if (exitCode) { 1892 return exitCode; 1893 } 1894 1895 exitCode = virtualenvSearch(search, result); 1896 if (exitCode) { 1897 return exitCode; 1898 } 1899 1900 // If we aren't collecting all items to list them, we can exit now. 1901 if (env && !(search->list || search->listPaths)) { 1902 return 0; 1903 } 1904 1905 for (struct RegistrySearchInfo *info = REGISTRY_SEARCH; info->subkey; ++info) { 1906 if (ERROR_SUCCESS == RegOpenKeyExW(info->hive, info->subkey, 0, info->flags, &root)) { 1907 exitCode = registrySearch(search, result, root, info->sortKey, info->fallbackArch); 1908 RegCloseKey(root); 1909 } 1910 if (exitCode) { 1911 return exitCode; 1912 } 1913 } 1914 1915 if (search->limitToCompany) { 1916 debug(L"# Skipping APPX search due to PYLAUNCHER_LIMIT_TO_COMPANY\n"); 1917 return 0; 1918 } 1919 1920 for (struct AppxSearchInfo *info = APPX_SEARCH; info->familyName; ++info) { 1921 exitCode = appxSearch(search, result, info->familyName, info->tag, info->sortKey); 1922 if (exitCode && exitCode != RC_NO_PYTHON) { 1923 return exitCode; 1924 } 1925 } 1926 1927 return 0; 1928} 1929 1930 1931/******************************************************************************\ 1932 *** INSTALL ON DEMAND *** 1933\******************************************************************************/ 1934 1935struct StoreSearchInfo { 1936 // The tag a user is looking for 1937 const wchar_t *tag; 1938 // The Store ID for a package if it can be installed from the Microsoft 1939 // Store. These are obtained from the dashboard at 1940 // https://partner.microsoft.com/dashboard 1941 const wchar_t *storeId; 1942}; 1943 1944 1945struct StoreSearchInfo STORE_SEARCH[] = { 1946 { L"3", /* 3.11 */ L"9NRWMJP3717K" }, 1947 { L"3.12", L"9NCVDN91XZQP" }, 1948 { L"3.11", L"9NRWMJP3717K" }, 1949 { L"3.10", L"9PJPW5LDXLZ5" }, 1950 { L"3.9", L"9P7QFQMJRFP7" }, 1951 { L"3.8", L"9MSSZTT1N39L" }, 1952 { NULL, NULL } 1953}; 1954 1955 1956int 1957_installEnvironment(const wchar_t *command, const wchar_t *arguments) 1958{ 1959 SHELLEXECUTEINFOW siw = { 1960 sizeof(SHELLEXECUTEINFOW), 1961 SEE_MASK_NOASYNC | SEE_MASK_NOCLOSEPROCESS | SEE_MASK_NO_CONSOLE, 1962 NULL, NULL, 1963 command, arguments, NULL, 1964 SW_SHOWNORMAL 1965 }; 1966 1967 debug(L"# Installing with %s %s\n", command, arguments); 1968 if (isEnvVarSet(L"PYLAUNCHER_DRYRUN")) { 1969 debug(L"# Exiting due to PYLAUNCHER_DRYRUN\n"); 1970 fflush(stdout); 1971 int mode = _setmode(_fileno(stdout), _O_U8TEXT); 1972 if (arguments) { 1973 fwprintf_s(stdout, L"\"%s\" %s\n", command, arguments); 1974 } else { 1975 fwprintf_s(stdout, L"\"%s\"\n", command); 1976 } 1977 fflush(stdout); 1978 if (mode >= 0) { 1979 _setmode(_fileno(stdout), mode); 1980 } 1981 return RC_INSTALLING; 1982 } 1983 1984 if (!ShellExecuteExW(&siw)) { 1985 return RC_NO_PYTHON; 1986 } 1987 1988 if (!siw.hProcess) { 1989 return RC_INSTALLING; 1990 } 1991 1992 WaitForSingleObjectEx(siw.hProcess, INFINITE, FALSE); 1993 DWORD exitCode = 0; 1994 if (GetExitCodeProcess(siw.hProcess, &exitCode) && exitCode == 0) { 1995 return 0; 1996 } 1997 return RC_INSTALLING; 1998} 1999 2000 2001const wchar_t *WINGET_COMMAND = L"Microsoft\\WindowsApps\\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe\\winget.exe"; 2002const wchar_t *WINGET_ARGUMENTS = L"install -q %s --exact --accept-package-agreements --source msstore"; 2003 2004const wchar_t *MSSTORE_COMMAND = L"ms-windows-store://pdp/?productid=%s"; 2005 2006int 2007installEnvironment(const SearchInfo *search) 2008{ 2009 // No tag? No installing 2010 if (!search->tag || !search->tagLength) { 2011 debug(L"# Cannot install Python with no tag specified\n"); 2012 return RC_NO_PYTHON; 2013 } 2014 2015 // PEP 514 tag but not PythonCore? No installing 2016 if (!search->oldStyleTag && 2017 search->company && search->companyLength && 2018 0 != _compare(search->company, search->companyLength, L"PythonCore", -1)) { 2019 debug(L"# Cannot install for company %.*s\n", search->companyLength, search->company); 2020 return RC_NO_PYTHON; 2021 } 2022 2023 const wchar_t *storeId = NULL; 2024 for (struct StoreSearchInfo *info = STORE_SEARCH; info->tag; ++info) { 2025 if (0 == _compare(search->tag, search->tagLength, info->tag, -1)) { 2026 storeId = info->storeId; 2027 break; 2028 } 2029 } 2030 2031 if (!storeId) { 2032 return RC_NO_PYTHON; 2033 } 2034 2035 int exitCode; 2036 wchar_t command[MAXLEN]; 2037 wchar_t arguments[MAXLEN]; 2038 if (SUCCEEDED(SHGetFolderPathW(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, command)) && 2039 join(command, MAXLEN, WINGET_COMMAND) && 2040 swprintf_s(arguments, MAXLEN, WINGET_ARGUMENTS, storeId)) { 2041 if (INVALID_FILE_ATTRIBUTES == GetFileAttributesW(command)) { 2042 formatWinerror(GetLastError(), arguments, MAXLEN); 2043 debug(L"# Skipping %s: %s\n", command, arguments); 2044 } else { 2045 fputws(L"Launching winget to install Python. The following output is from the install process\n\ 2046***********************************************************************\n", stdout); 2047 exitCode = _installEnvironment(command, arguments); 2048 if (exitCode == RC_INSTALLING) { 2049 fputws(L"***********************************************************************\n\ 2050Please check the install status and run your command again.", stderr); 2051 return exitCode; 2052 } else if (exitCode) { 2053 return exitCode; 2054 } 2055 fputws(L"***********************************************************************\n\ 2056Install appears to have succeeded. Searching for new matching installs.\n", stdout); 2057 return 0; 2058 } 2059 } 2060 2061 if (swprintf_s(command, MAXLEN, MSSTORE_COMMAND, storeId)) { 2062 fputws(L"Opening the Microsoft Store to install Python. After installation, " 2063 L"please run your command again.\n", stderr); 2064 exitCode = _installEnvironment(command, NULL); 2065 if (exitCode) { 2066 return exitCode; 2067 } 2068 return 0; 2069 } 2070 2071 return RC_NO_PYTHON; 2072} 2073 2074/******************************************************************************\ 2075 *** ENVIRONMENT SELECT *** 2076\******************************************************************************/ 2077 2078bool 2079_companyMatches(const SearchInfo *search, const EnvironmentInfo *env) 2080{ 2081 if (!search->company || !search->companyLength) { 2082 return true; 2083 } 2084 return 0 == _compare(env->company, -1, search->company, search->companyLength); 2085} 2086 2087 2088bool 2089_tagMatches(const SearchInfo *search, const EnvironmentInfo *env, int searchTagLength) 2090{ 2091 if (searchTagLength < 0) { 2092 searchTagLength = search->tagLength; 2093 } 2094 if (!search->tag || !searchTagLength) { 2095 return true; 2096 } 2097 return _startsWithSeparated(env->tag, -1, search->tag, searchTagLength, L".-"); 2098} 2099 2100 2101bool 2102_is32Bit(const EnvironmentInfo *env) 2103{ 2104 if (env->architecture) { 2105 return 0 == _compare(env->architecture, -1, L"32bit", -1); 2106 } 2107 return false; 2108} 2109 2110 2111int 2112_selectEnvironment(const SearchInfo *search, EnvironmentInfo *env, EnvironmentInfo **best) 2113{ 2114 int exitCode = 0; 2115 while (env) { 2116 exitCode = _selectEnvironment(search, env->prev, best); 2117 2118 if (exitCode && exitCode != RC_NO_PYTHON) { 2119 return exitCode; 2120 } else if (!exitCode && *best) { 2121 return 0; 2122 } 2123 2124 if (env->highPriority && search->lowPriorityTag) { 2125 // This environment is marked high priority, and the search allows 2126 // it to be selected even though a tag is specified, so select it 2127 // gh-92817: this allows an active venv to be selected even when a 2128 // default tag has been found in py.ini or the environment 2129 *best = env; 2130 return 0; 2131 } 2132 2133 if (!search->oldStyleTag) { 2134 if (_companyMatches(search, env) && _tagMatches(search, env, -1)) { 2135 // Because of how our sort tree is set up, we will walk up the 2136 // "prev" side and implicitly select the "best" best. By 2137 // returning straight after a match, we skip the entire "next" 2138 // branch and won't ever select a "worse" best. 2139 *best = env; 2140 return 0; 2141 } 2142 } else if (0 == _compare(env->company, -1, L"PythonCore", -1)) { 2143 // Old-style tags can only match PythonCore entries 2144 2145 // If the tag ends with -64, we want to exclude 32-bit runtimes 2146 // (If the tag ends with -32, it will be filtered later) 2147 int tagLength = search->tagLength; 2148 bool exclude32Bit = false, only32Bit = false; 2149 if (tagLength > 3) { 2150 if (0 == _compareArgument(&search->tag[tagLength - 3], 3, L"-64", 3)) { 2151 tagLength -= 3; 2152 exclude32Bit = true; 2153 } else if (0 == _compareArgument(&search->tag[tagLength - 3], 3, L"-32", 3)) { 2154 tagLength -= 3; 2155 only32Bit = true; 2156 } 2157 } 2158 2159 if (_tagMatches(search, env, tagLength)) { 2160 if (exclude32Bit && _is32Bit(env)) { 2161 debug(L"# Excluding %s/%s because it looks like 32bit\n", env->company, env->tag); 2162 } else if (only32Bit && !_is32Bit(env)) { 2163 debug(L"# Excluding %s/%s because it doesn't look 32bit\n", env->company, env->tag); 2164 } else { 2165 *best = env; 2166 return 0; 2167 } 2168 } 2169 } 2170 2171 env = env->next; 2172 } 2173 return RC_NO_PYTHON; 2174} 2175 2176int 2177selectEnvironment(const SearchInfo *search, EnvironmentInfo *root, EnvironmentInfo **best) 2178{ 2179 if (!best) { 2180 return RC_INTERNAL_ERROR; 2181 } 2182 if (!root) { 2183 *best = NULL; 2184 return RC_NO_PYTHON_AT_ALL; 2185 } 2186 2187 EnvironmentInfo *result = NULL; 2188 int exitCode = _selectEnvironment(search, root, &result); 2189 if (!exitCode) { 2190 *best = result; 2191 } 2192 2193 return exitCode; 2194} 2195 2196 2197/******************************************************************************\ 2198 *** LIST ENVIRONMENTS *** 2199\******************************************************************************/ 2200 2201#define TAGWIDTH 16 2202 2203int 2204_printEnvironment(const EnvironmentInfo *env, FILE *out, bool showPath, const wchar_t *argument) 2205{ 2206 if (showPath) { 2207 if (env->executablePath && env->executablePath[0]) { 2208 if (env->executableArgs && env->executableArgs[0]) { 2209 fwprintf(out, L" %-*s %s %s\n", TAGWIDTH, argument, env->executablePath, env->executableArgs); 2210 } else { 2211 fwprintf(out, L" %-*s %s\n", TAGWIDTH, argument, env->executablePath); 2212 } 2213 } else if (env->installDir && env->installDir[0]) { 2214 fwprintf(out, L" %-*s %s\n", TAGWIDTH, argument, env->installDir); 2215 } else { 2216 fwprintf(out, L" %s\n", argument); 2217 } 2218 } else if (env->displayName) { 2219 fwprintf(out, L" %-*s %s\n", TAGWIDTH, argument, env->displayName); 2220 } else { 2221 fwprintf(out, L" %s\n", argument); 2222 } 2223 return 0; 2224} 2225 2226 2227int 2228_listAllEnvironments(EnvironmentInfo *env, FILE * out, bool showPath, EnvironmentInfo *defaultEnv) 2229{ 2230 wchar_t buffer[256]; 2231 const int bufferSize = 256; 2232 while (env) { 2233 int exitCode = _listAllEnvironments(env->prev, out, showPath, defaultEnv); 2234 if (exitCode) { 2235 return exitCode; 2236 } 2237 2238 if (!env->company || !env->tag) { 2239 buffer[0] = L'\0'; 2240 } else if (0 == _compare(env->company, -1, L"PythonCore", -1)) { 2241 swprintf_s(buffer, bufferSize, L"-V:%s", env->tag); 2242 } else { 2243 swprintf_s(buffer, bufferSize, L"-V:%s/%s", env->company, env->tag); 2244 } 2245 2246 if (env == defaultEnv) { 2247 wcscat_s(buffer, bufferSize, L" *"); 2248 } 2249 2250 if (buffer[0]) { 2251 exitCode = _printEnvironment(env, out, showPath, buffer); 2252 if (exitCode) { 2253 return exitCode; 2254 } 2255 } 2256 2257 env = env->next; 2258 } 2259 return 0; 2260} 2261 2262 2263int 2264listEnvironments(EnvironmentInfo *env, FILE * out, bool showPath, EnvironmentInfo *defaultEnv) 2265{ 2266 if (!env) { 2267 fwprintf_s(stdout, L"No installed Pythons found!\n"); 2268 return 0; 2269 } 2270 2271 /* TODO: Do we want to display these? 2272 In favour, helps users see that '-3' is a good option 2273 Against, repeats the next line of output 2274 SearchInfo majorSearch; 2275 EnvironmentInfo *major; 2276 int exitCode; 2277 2278 if (showPath) { 2279 memset(&majorSearch, 0, sizeof(majorSearch)); 2280 majorSearch.company = L"PythonCore"; 2281 majorSearch.companyLength = -1; 2282 majorSearch.tag = L"3"; 2283 majorSearch.tagLength = -1; 2284 majorSearch.oldStyleTag = true; 2285 major = NULL; 2286 exitCode = selectEnvironment(&majorSearch, env, &major); 2287 if (!exitCode && major) { 2288 exitCode = _printEnvironment(major, out, showPath, L"-3 *"); 2289 isDefault = false; 2290 if (exitCode) { 2291 return exitCode; 2292 } 2293 } 2294 majorSearch.tag = L"2"; 2295 major = NULL; 2296 exitCode = selectEnvironment(&majorSearch, env, &major); 2297 if (!exitCode && major) { 2298 exitCode = _printEnvironment(major, out, showPath, L"-2"); 2299 if (exitCode) { 2300 return exitCode; 2301 } 2302 } 2303 } 2304 */ 2305 2306 int mode = _setmode(_fileno(out), _O_U8TEXT); 2307 int exitCode = _listAllEnvironments(env, out, showPath, defaultEnv); 2308 fflush(out); 2309 if (mode >= 0) { 2310 _setmode(_fileno(out), mode); 2311 } 2312 return exitCode; 2313} 2314 2315 2316/******************************************************************************\ 2317 *** INTERPRETER LAUNCH *** 2318\******************************************************************************/ 2319 2320 2321int 2322calculateCommandLine(const SearchInfo *search, const EnvironmentInfo *launch, wchar_t *buffer, int bufferLength) 2323{ 2324 int exitCode = 0; 2325 const wchar_t *executablePath = NULL; 2326 2327 // Construct command line from a search override, or else the selected 2328 // environment's executablePath 2329 if (search->executablePath) { 2330 executablePath = search->executablePath; 2331 } else if (launch && launch->executablePath) { 2332 executablePath = launch->executablePath; 2333 } 2334 2335 // If we have an executable path, put it at the start of the command, but 2336 // only if the search allowed an override. 2337 // Otherwise, use the environment's installDir and the search's default 2338 // executable name. 2339 if (executablePath && search->allowExecutableOverride) { 2340 if (wcschr(executablePath, L' ') && executablePath[0] != L'"') { 2341 buffer[0] = L'"'; 2342 exitCode = wcscpy_s(&buffer[1], bufferLength - 1, executablePath); 2343 if (!exitCode) { 2344 exitCode = wcscat_s(buffer, bufferLength, L"\""); 2345 } 2346 } else { 2347 exitCode = wcscpy_s(buffer, bufferLength, executablePath); 2348 } 2349 } else if (launch) { 2350 if (!launch->installDir) { 2351 fwprintf_s(stderr, L"Cannot launch %s %s because no install directory was specified", 2352 launch->company, launch->tag); 2353 exitCode = RC_NO_PYTHON; 2354 } else if (!search->executable || !search->executableLength) { 2355 fwprintf_s(stderr, L"Cannot launch %s %s because no executable name is available", 2356 launch->company, launch->tag); 2357 exitCode = RC_NO_PYTHON; 2358 } else { 2359 wchar_t executable[256]; 2360 wcsncpy_s(executable, 256, search->executable, search->executableLength); 2361 if ((wcschr(launch->installDir, L' ') && launch->installDir[0] != L'"') || 2362 (wcschr(executable, L' ') && executable[0] != L'"')) { 2363 buffer[0] = L'"'; 2364 exitCode = wcscpy_s(&buffer[1], bufferLength - 1, launch->installDir); 2365 if (!exitCode) { 2366 exitCode = join(buffer, bufferLength, executable) ? 0 : RC_NO_MEMORY; 2367 } 2368 if (!exitCode) { 2369 exitCode = wcscat_s(buffer, bufferLength, L"\""); 2370 } 2371 } else { 2372 exitCode = wcscpy_s(buffer, bufferLength, launch->installDir); 2373 if (!exitCode) { 2374 exitCode = join(buffer, bufferLength, executable) ? 0 : RC_NO_MEMORY; 2375 } 2376 } 2377 } 2378 } else { 2379 exitCode = RC_NO_PYTHON; 2380 } 2381 2382 if (!exitCode && launch && launch->executableArgs) { 2383 exitCode = wcscat_s(buffer, bufferLength, L" "); 2384 if (!exitCode) { 2385 exitCode = wcscat_s(buffer, bufferLength, launch->executableArgs); 2386 } 2387 } 2388 2389 if (!exitCode && search->executableArgs) { 2390 if (search->executableArgsLength < 0) { 2391 exitCode = wcscat_s(buffer, bufferLength, search->executableArgs); 2392 } else if (search->executableArgsLength > 0) { 2393 int end = (int)wcsnlen_s(buffer, MAXLEN); 2394 if (end < bufferLength - (search->executableArgsLength + 1)) { 2395 exitCode = wcsncpy_s(&buffer[end], bufferLength - end, 2396 search->executableArgs, search->executableArgsLength); 2397 } 2398 } 2399 } 2400 2401 if (!exitCode && search->restOfCmdLine) { 2402 exitCode = wcscat_s(buffer, bufferLength, search->restOfCmdLine); 2403 } 2404 2405 return exitCode; 2406} 2407 2408 2409 2410BOOL 2411_safeDuplicateHandle(HANDLE in, HANDLE * pout, const wchar_t *nameForError) 2412{ 2413 BOOL ok; 2414 HANDLE process = GetCurrentProcess(); 2415 DWORD rc; 2416 2417 *pout = NULL; 2418 ok = DuplicateHandle(process, in, process, pout, 0, TRUE, 2419 DUPLICATE_SAME_ACCESS); 2420 if (!ok) { 2421 rc = GetLastError(); 2422 if (rc == ERROR_INVALID_HANDLE) { 2423 debug(L"DuplicateHandle returned ERROR_INVALID_HANDLE\n"); 2424 ok = TRUE; 2425 } 2426 else { 2427 winerror(0, L"Failed to duplicate %s handle", nameForError); 2428 } 2429 } 2430 return ok; 2431} 2432 2433BOOL WINAPI 2434ctrl_c_handler(DWORD code) 2435{ 2436 return TRUE; /* We just ignore all control events. */ 2437} 2438 2439 2440int 2441launchEnvironment(const SearchInfo *search, const EnvironmentInfo *launch, wchar_t *launchCommand) 2442{ 2443 HANDLE job; 2444 JOBOBJECT_EXTENDED_LIMIT_INFORMATION info; 2445 DWORD rc; 2446 BOOL ok; 2447 STARTUPINFOW si; 2448 PROCESS_INFORMATION pi; 2449 2450 // If this is a dryrun, do not actually launch 2451 if (isEnvVarSet(L"PYLAUNCHER_DRYRUN")) { 2452 debug(L"LaunchCommand: %s\n", launchCommand); 2453 debug(L"# Exiting due to PYLAUNCHER_DRYRUN variable\n"); 2454 fflush(stdout); 2455 int mode = _setmode(_fileno(stdout), _O_U8TEXT); 2456 fwprintf(stdout, L"%s\n", launchCommand); 2457 fflush(stdout); 2458 if (mode >= 0) { 2459 _setmode(_fileno(stdout), mode); 2460 } 2461 return 0; 2462 } 2463 2464#if defined(_WINDOWS) 2465 /* 2466 When explorer launches a Windows (GUI) application, it displays 2467 the "app starting" (the "pointer + hourglass") cursor for a number 2468 of seconds, or until the app does something UI-ish (eg, creating a 2469 window, or fetching a message). As this launcher doesn't do this 2470 directly, that cursor remains even after the child process does these 2471 things. We avoid that by doing a simple post+get message. 2472 See http://bugs.python.org/issue17290 and 2473 https://bitbucket.org/vinay.sajip/pylauncher/issue/20/busy-cursor-for-a-long-time-when-running 2474 */ 2475 MSG msg; 2476 2477 PostMessage(0, 0, 0, 0); 2478 GetMessage(&msg, 0, 0, 0); 2479#endif 2480 2481 debug(L"# about to run: %s\n", launchCommand); 2482 job = CreateJobObject(NULL, NULL); 2483 ok = QueryInformationJobObject(job, JobObjectExtendedLimitInformation, 2484 &info, sizeof(info), &rc); 2485 if (!ok || (rc != sizeof(info)) || !job) { 2486 winerror(0, L"Failed to query job information"); 2487 return RC_CREATE_PROCESS; 2488 } 2489 info.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE | 2490 JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK; 2491 ok = SetInformationJobObject(job, JobObjectExtendedLimitInformation, &info, 2492 sizeof(info)); 2493 if (!ok) { 2494 winerror(0, L"Failed to update job information"); 2495 return RC_CREATE_PROCESS; 2496 } 2497 memset(&si, 0, sizeof(si)); 2498 GetStartupInfoW(&si); 2499 if (!_safeDuplicateHandle(GetStdHandle(STD_INPUT_HANDLE), &si.hStdInput, L"stdin") || 2500 !_safeDuplicateHandle(GetStdHandle(STD_OUTPUT_HANDLE), &si.hStdOutput, L"stdout") || 2501 !_safeDuplicateHandle(GetStdHandle(STD_ERROR_HANDLE), &si.hStdError, L"stderr")) { 2502 return RC_NO_STD_HANDLES; 2503 } 2504 2505 ok = SetConsoleCtrlHandler(ctrl_c_handler, TRUE); 2506 if (!ok) { 2507 winerror(0, L"Failed to update Control-C handler"); 2508 return RC_NO_STD_HANDLES; 2509 } 2510 2511 si.dwFlags = STARTF_USESTDHANDLES; 2512 ok = CreateProcessW(NULL, launchCommand, NULL, NULL, TRUE, 2513 0, NULL, NULL, &si, &pi); 2514 if (!ok) { 2515 winerror(0, L"Unable to create process using '%s'", launchCommand); 2516 return RC_CREATE_PROCESS; 2517 } 2518 AssignProcessToJobObject(job, pi.hProcess); 2519 CloseHandle(pi.hThread); 2520 WaitForSingleObjectEx(pi.hProcess, INFINITE, FALSE); 2521 ok = GetExitCodeProcess(pi.hProcess, &rc); 2522 if (!ok) { 2523 winerror(0, L"Failed to get exit code of process"); 2524 return RC_CREATE_PROCESS; 2525 } 2526 debug(L"child process exit code: %d\n", rc); 2527 return rc; 2528} 2529 2530 2531/******************************************************************************\ 2532 *** PROCESS CONTROLLER *** 2533\******************************************************************************/ 2534 2535 2536int 2537performSearch(SearchInfo *search, EnvironmentInfo **envs) 2538{ 2539 // First parse the command line for options 2540 int exitCode = parseCommandLine(search); 2541 if (exitCode) { 2542 return exitCode; 2543 } 2544 2545 // Check for a shebang line in our script file 2546 // (or return quickly if no script file was specified) 2547 exitCode = checkShebang(search); 2548 switch (exitCode) { 2549 case 0: 2550 case RC_NO_SHEBANG: 2551 case RC_RECURSIVE_SHEBANG: 2552 break; 2553 default: 2554 return exitCode; 2555 } 2556 2557 // Resolve old-style tags (possibly from a shebang) against py.ini entries 2558 // and environment variables. 2559 exitCode = checkDefaults(search); 2560 if (exitCode) { 2561 return exitCode; 2562 } 2563 2564 // If debugging is enabled, list our search criteria 2565 dumpSearchInfo(search); 2566 2567 // Find all matching environments 2568 exitCode = collectEnvironments(search, envs); 2569 if (exitCode) { 2570 return exitCode; 2571 } 2572 2573 return 0; 2574} 2575 2576 2577int 2578process(int argc, wchar_t ** argv) 2579{ 2580 int exitCode = 0; 2581 int searchExitCode = 0; 2582 SearchInfo search = {0}; 2583 EnvironmentInfo *envs = NULL; 2584 EnvironmentInfo *env = NULL; 2585 wchar_t launchCommand[MAXLEN]; 2586 2587 memset(launchCommand, 0, sizeof(launchCommand)); 2588 2589 if (isEnvVarSet(L"PYLAUNCHER_DEBUG")) { 2590 setvbuf(stderr, (char *)NULL, _IONBF, 0); 2591 log_fp = stderr; 2592 debug(L"argv0: %s\nversion: %S\n", argv[0], PY_VERSION); 2593 } 2594 2595 DWORD len = GetEnvironmentVariableW(L"PYLAUNCHER_LIMIT_TO_COMPANY", NULL, 0); 2596 if (len > 1) { 2597 wchar_t *limitToCompany = allocSearchInfoBuffer(&search, len); 2598 search.limitToCompany = limitToCompany; 2599 if (0 == GetEnvironmentVariableW(L"PYLAUNCHER_LIMIT_TO_COMPANY", limitToCompany, len)) { 2600 exitCode = RC_INTERNAL_ERROR; 2601 winerror(0, L"Failed to read PYLAUNCHER_LIMIT_TO_COMPANY variable"); 2602 goto abort; 2603 } 2604 } 2605 2606 search.originalCmdLine = GetCommandLineW(); 2607 2608 exitCode = performSearch(&search, &envs); 2609 if (exitCode) { 2610 goto abort; 2611 } 2612 2613 // Display the help text, but only exit on error 2614 if (search.help) { 2615 exitCode = showHelpText(argv); 2616 if (exitCode) { 2617 goto abort; 2618 } 2619 } 2620 2621 // Select best environment 2622 // This is early so that we can show the default when listing, but all 2623 // responses to any errors occur later. 2624 searchExitCode = selectEnvironment(&search, envs, &env); 2625 2626 // List all environments, then exit 2627 if (search.list || search.listPaths) { 2628 exitCode = listEnvironments(envs, stdout, search.listPaths, env); 2629 goto abort; 2630 } 2631 2632 // When debugging, list all discovered environments anyway 2633 if (log_fp) { 2634 exitCode = listEnvironments(envs, log_fp, true, NULL); 2635 if (exitCode) { 2636 goto abort; 2637 } 2638 } 2639 2640 // We searched earlier, so if we didn't find anything, now we react 2641 exitCode = searchExitCode; 2642 // If none found, and if permitted, install it 2643 if (exitCode == RC_NO_PYTHON && isEnvVarSet(L"PYLAUNCHER_ALLOW_INSTALL") || 2644 isEnvVarSet(L"PYLAUNCHER_ALWAYS_INSTALL")) { 2645 exitCode = installEnvironment(&search); 2646 if (!exitCode) { 2647 // Successful install, so we need to re-scan and select again 2648 env = NULL; 2649 exitCode = performSearch(&search, &envs); 2650 if (exitCode) { 2651 goto abort; 2652 } 2653 exitCode = selectEnvironment(&search, envs, &env); 2654 } 2655 } 2656 if (exitCode == RC_NO_PYTHON) { 2657 fputws(L"No suitable Python runtime found\n", stderr); 2658 fputws(L"Pass --list (-0) to see all detected environments on your machine\n", stderr); 2659 if (!isEnvVarSet(L"PYLAUNCHER_ALLOW_INSTALL") && search.oldStyleTag) { 2660 fputws(L"or set environment variable PYLAUNCHER_ALLOW_INSTALL to use winget\n" 2661 L"or open the Microsoft Store to the requested version.\n", stderr); 2662 } 2663 goto abort; 2664 } 2665 if (exitCode == RC_NO_PYTHON_AT_ALL) { 2666 fputws(L"No installed Python found!\n", stderr); 2667 goto abort; 2668 } 2669 if (exitCode) { 2670 goto abort; 2671 } 2672 2673 if (env) { 2674 debug(L"env.company: %s\nenv.tag: %s\n", env->company, env->tag); 2675 } else { 2676 debug(L"env.company: (null)\nenv.tag: (null)\n"); 2677 } 2678 2679 exitCode = calculateCommandLine(&search, env, launchCommand, sizeof(launchCommand) / sizeof(launchCommand[0])); 2680 if (exitCode) { 2681 goto abort; 2682 } 2683 2684 // Launch selected runtime 2685 exitCode = launchEnvironment(&search, env, launchCommand); 2686 2687abort: 2688 freeSearchInfo(&search); 2689 freeEnvironmentInfo(envs); 2690 return exitCode; 2691} 2692 2693 2694#if defined(_WINDOWS) 2695 2696int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, 2697 LPWSTR lpstrCmd, int nShow) 2698{ 2699 return process(__argc, __wargv); 2700} 2701 2702#else 2703 2704int cdecl wmain(int argc, wchar_t ** argv) 2705{ 2706 return process(argc, argv); 2707} 2708 2709#endif 2710