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