[Python-checkins] bpo-32030: Rewrite calculate_path() (#4521)

Victor Stinner webhook-mailer at python.org
Thu Nov 23 11:03:23 EST 2017


https://github.com/python/cpython/commit/0327bde9da203bb256b58218d012ca76ad0db4e4
commit: 0327bde9da203bb256b58218d012ca76ad0db4e4
branch: master
author: Victor Stinner <victor.stinner at gmail.com>
committer: GitHub <noreply at github.com>
date: 2017-11-23T17:03:20+01:00
summary:

bpo-32030: Rewrite calculate_path() (#4521)

* calculate_path() rewritten in Modules/getpath.c and PC/getpathp.c
* Move global variables into a new PyPathConfig structure.
* calculate_path():

  * Split the huge calculate_path() function into subfunctions.
  * Add PyCalculatePath structure to pass data between subfunctions.
  * Document PyCalculatePath fields.
  * Move cleanup code into a new calculate_free() subfunction
  * calculate_init() now handles Py_DecodeLocale() failures properly
  * calculate_path() is now atomic: only replace PyPathConfig
    (path_config) at once on success.

* _Py_GetPythonHomeWithConfig() now returns an error on failure
* Add _Py_INIT_NO_MEMORY() helper: report a memory allocation failure
* Coding style fixes (PEP 7)

files:
M Include/pylifecycle.h
M Modules/getpath.c
M Modules/main.c
M PC/getpathp.c
M Python/pylifecycle.c

diff --git a/Include/pylifecycle.h b/Include/pylifecycle.h
index 3b603c87ad4..efdc58eed8e 100644
--- a/Include/pylifecycle.h
+++ b/Include/pylifecycle.h
@@ -7,23 +7,7 @@
 extern "C" {
 #endif
 
-PyAPI_FUNC(void) Py_SetProgramName(wchar_t *);
-PyAPI_FUNC(wchar_t *) Py_GetProgramName(void);
-
-PyAPI_FUNC(void) Py_SetPythonHome(wchar_t *);
-PyAPI_FUNC(wchar_t *) Py_GetPythonHome(void);
-#ifdef Py_BUILD_CORE
-PyAPI_FUNC(wchar_t *) _Py_GetPythonHomeWithConfig(
-    const _PyMainInterpreterConfig *config);
-#endif
-
 #ifndef Py_LIMITED_API
-/* Only used by applications that embed the interpreter and need to
- * override the standard encoding determination mechanism
- */
-PyAPI_FUNC(int) Py_SetStandardStreamEncoding(const char *encoding,
-                                             const char *errors);
-
 typedef struct {
     const char *prefix;
     const char *msg;
@@ -46,9 +30,31 @@ typedef struct {
    Don't abort() the process on such error. */
 #define _Py_INIT_USER_ERR(MSG) \
     (_PyInitError){.prefix = _Py_INIT_GET_FUNC(), .msg = (MSG), .user_err = 1}
+#define _Py_INIT_NO_MEMORY() _Py_INIT_ERR("memory allocation failed")
 #define _Py_INIT_FAILED(err) \
     (err.msg != NULL)
 
+#endif
+
+
+PyAPI_FUNC(void) Py_SetProgramName(wchar_t *);
+PyAPI_FUNC(wchar_t *) Py_GetProgramName(void);
+
+PyAPI_FUNC(void) Py_SetPythonHome(wchar_t *);
+PyAPI_FUNC(wchar_t *) Py_GetPythonHome(void);
+#ifdef Py_BUILD_CORE
+PyAPI_FUNC(_PyInitError) _Py_GetPythonHomeWithConfig(
+    const _PyMainInterpreterConfig *config,
+    wchar_t **home);
+#endif
+
+#ifndef Py_LIMITED_API
+/* Only used by applications that embed the interpreter and need to
+ * override the standard encoding determination mechanism
+ */
+PyAPI_FUNC(int) Py_SetStandardStreamEncoding(const char *encoding,
+                                             const char *errors);
+
 /* PEP 432 Multi-phase initialization API (Private while provisional!) */
 PyAPI_FUNC(_PyInitError) _Py_InitializeCore(const _PyCoreConfig *);
 PyAPI_FUNC(int) _Py_IsCoreInitialized(void);
diff --git a/Modules/getpath.c b/Modules/getpath.c
index 62f5e695849..d8125ae2cac 100644
--- a/Modules/getpath.c
+++ b/Modules/getpath.c
@@ -7,7 +7,7 @@
 #include <string.h>
 
 #ifdef __APPLE__
-#include <mach-o/dyld.h>
+#  include <mach-o/dyld.h>
 #endif
 
 /* Search in some common locations for the associated Python libraries.
@@ -97,7 +97,7 @@
  */
 
 #ifdef __cplusplus
- extern "C" {
+extern "C" {
 #endif
 
 
@@ -109,13 +109,38 @@
 #define LANDMARK L"os.py"
 #endif
 
-static wchar_t prefix[MAXPATHLEN+1];
-static wchar_t exec_prefix[MAXPATHLEN+1];
-static wchar_t progpath[MAXPATHLEN+1];
-static wchar_t *module_search_path = NULL;
+typedef struct {
+    wchar_t prefix[MAXPATHLEN+1];
+    wchar_t exec_prefix[MAXPATHLEN+1];
+    wchar_t progpath[MAXPATHLEN+1];
+    wchar_t *module_search_path;
+} PyPathConfig;
+
+typedef struct {
+    wchar_t *path_env;                 /* PATH environment variable */
+    wchar_t *home;                     /* PYTHONHOME environment variable */
+    wchar_t *module_search_path_env;   /* PYTHONPATH environment variable */
+    wchar_t *module_search_path_buffer;
+
+    wchar_t *prog;                     /* Program name */
+    wchar_t *pythonpath;               /* PYTHONPATH define */
+    wchar_t *prefix;                   /* PREFIX define */
+    wchar_t *exec_prefix;              /* EXEC_PREFIX define */
+
+    wchar_t *lib_python;               /* "lib/pythonX.Y" */
+    wchar_t argv0_path[MAXPATHLEN+1];
+    wchar_t zip_path[MAXPATHLEN+1];    /* ".../lib/pythonXY.zip" */
+
+    int prefix_found;         /* found platform independent libraries? */
+    int exec_prefix_found;    /* found the platform dependent libraries? */
+} PyCalculatePath;
+
+static const wchar_t delimiter[2] = {DELIM, '\0'};
+static const wchar_t separator[2] = {SEP, '\0'};
+static PyPathConfig path_config = {.module_search_path = NULL};
 
-/* Get file status. Encode the path to the locale encoding. */
 
+/* Get file status. Encode the path to the locale encoding. */
 static int
 _Py_wstat(const wchar_t* path, struct stat *buf)
 {
@@ -131,6 +156,7 @@ _Py_wstat(const wchar_t* path, struct stat *buf)
     return err;
 }
 
+
 static void
 reduce(wchar_t *dir)
 {
@@ -140,14 +166,17 @@ reduce(wchar_t *dir)
     dir[i] = '\0';
 }
 
+
 static int
 isfile(wchar_t *filename)          /* Is file, not directory */
 {
     struct stat buf;
-    if (_Py_wstat(filename, &buf) != 0)
+    if (_Py_wstat(filename, &buf) != 0) {
         return 0;
-    if (!S_ISREG(buf.st_mode))
+    }
+    if (!S_ISREG(buf.st_mode)) {
         return 0;
+    }
     return 1;
 }
 
@@ -155,41 +184,50 @@ isfile(wchar_t *filename)          /* Is file, not directory */
 static int
 ismodule(wchar_t *filename)        /* Is module -- check for .pyc too */
 {
-    if (isfile(filename))
+    if (isfile(filename)) {
         return 1;
+    }
 
     /* Check for the compiled version of prefix. */
     if (wcslen(filename) < MAXPATHLEN) {
         wcscat(filename, L"c");
-        if (isfile(filename))
+        if (isfile(filename)) {
             return 1;
+        }
     }
     return 0;
 }
 
 
+/* Is executable file */
 static int
-isxfile(wchar_t *filename)         /* Is executable file */
+isxfile(wchar_t *filename)
 {
     struct stat buf;
-    if (_Py_wstat(filename, &buf) != 0)
+    if (_Py_wstat(filename, &buf) != 0) {
         return 0;
-    if (!S_ISREG(buf.st_mode))
+    }
+    if (!S_ISREG(buf.st_mode)) {
         return 0;
-    if ((buf.st_mode & 0111) == 0)
+    }
+    if ((buf.st_mode & 0111) == 0) {
         return 0;
+    }
     return 1;
 }
 
 
+/* Is directory */
 static int
-isdir(wchar_t *filename)                   /* Is directory */
+isdir(wchar_t *filename)
 {
     struct stat buf;
-    if (_Py_wstat(filename, &buf) != 0)
+    if (_Py_wstat(filename, &buf) != 0) {
         return 0;
-    if (!S_ISDIR(buf.st_mode))
+    }
+    if (!S_ISDIR(buf.st_mode)) {
         return 0;
+    }
     return 1;
 }
 
@@ -207,58 +245,67 @@ static void
 joinpath(wchar_t *buffer, wchar_t *stuff)
 {
     size_t n, k;
-    if (stuff[0] == SEP)
+    if (stuff[0] == SEP) {
         n = 0;
+    }
     else {
         n = wcslen(buffer);
-        if (n > 0 && buffer[n-1] != SEP && n < MAXPATHLEN)
+        if (n > 0 && buffer[n-1] != SEP && n < MAXPATHLEN) {
             buffer[n++] = SEP;
+        }
     }
-    if (n > MAXPATHLEN)
+    if (n > MAXPATHLEN) {
         Py_FatalError("buffer overflow in getpath.c's joinpath()");
+    }
     k = wcslen(stuff);
-    if (n + k > MAXPATHLEN)
+    if (n + k > MAXPATHLEN) {
         k = MAXPATHLEN - n;
+    }
     wcsncpy(buffer+n, stuff, k);
     buffer[n+k] = '\0';
 }
 
+
 /* copy_absolute requires that path be allocated at least
    MAXPATHLEN + 1 bytes and that p be no more than MAXPATHLEN bytes. */
 static void
 copy_absolute(wchar_t *path, wchar_t *p, size_t pathlen)
 {
-    if (p[0] == SEP)
+    if (p[0] == SEP) {
         wcscpy(path, p);
+    }
     else {
         if (!_Py_wgetcwd(path, pathlen)) {
             /* unable to get the current directory */
             wcscpy(path, p);
             return;
         }
-        if (p[0] == '.' && p[1] == SEP)
+        if (p[0] == '.' && p[1] == SEP) {
             p += 2;
+        }
         joinpath(path, p);
     }
 }
 
+
 /* absolutize() requires that path be allocated at least MAXPATHLEN+1 bytes. */
 static void
 absolutize(wchar_t *path)
 {
     wchar_t buffer[MAXPATHLEN+1];
 
-    if (path[0] == SEP)
+    if (path[0] == SEP) {
         return;
+    }
     copy_absolute(buffer, path, MAXPATHLEN+1);
     wcscpy(path, buffer);
 }
 
+
 /* search for a prefix value in an environment file. If found, copy it
    to the provided buffer, which is expected to be no more than MAXPATHLEN
    bytes long.
 */
-
 static int
 find_env_config_value(FILE * env_file, const wchar_t * key, wchar_t * value)
 {
@@ -272,15 +319,18 @@ find_env_config_value(FILE * env_file, const wchar_t * key, wchar_t * value)
         PyObject * decoded;
         int n;
 
-        if (p == NULL)
+        if (p == NULL) {
             break;
+        }
         n = strlen(p);
         if (p[n - 1] != '\n') {
             /* line has overflowed - bail */
             break;
         }
-        if (p[0] == '#')    /* Comment - skip */
+        if (p[0] == '#') {
+            /* Comment - skip */
             continue;
+        }
         decoded = PyUnicode_DecodeUTF8(buffer, n, "surrogateescape");
         if (decoded != NULL) {
             Py_ssize_t k;
@@ -307,106 +357,151 @@ find_env_config_value(FILE * env_file, const wchar_t * key, wchar_t * value)
     return result;
 }
 
+
 /* search_for_prefix requires that argv0_path be no more than MAXPATHLEN
    bytes long.
 */
 static int
-search_for_prefix(wchar_t *argv0_path, wchar_t *home, wchar_t *_prefix,
-                  wchar_t *lib_python)
+search_for_prefix(PyCalculatePath *calculate, PyPathConfig *config)
 {
     size_t n;
     wchar_t *vpath;
 
     /* If PYTHONHOME is set, we believe it unconditionally */
-    if (home) {
-        wchar_t *delim;
-        wcsncpy(prefix, home, MAXPATHLEN);
-        prefix[MAXPATHLEN] = L'\0';
-        delim = wcschr(prefix, DELIM);
-        if (delim)
+    if (calculate->home) {
+        wcsncpy(config->prefix, calculate->home, MAXPATHLEN);
+        config->prefix[MAXPATHLEN] = L'\0';
+        wchar_t *delim = wcschr(config->prefix, DELIM);
+        if (delim) {
             *delim = L'\0';
-        joinpath(prefix, lib_python);
-        joinpath(prefix, LANDMARK);
+        }
+        joinpath(config->prefix, calculate->lib_python);
+        joinpath(config->prefix, LANDMARK);
         return 1;
     }
 
     /* Check to see if argv[0] is in the build directory */
-    wcsncpy(prefix, argv0_path, MAXPATHLEN);
-    prefix[MAXPATHLEN] = L'\0';
-    joinpath(prefix, L"Modules/Setup");
-    if (isfile(prefix)) {
+    wcsncpy(config->prefix, calculate->argv0_path, MAXPATHLEN);
+    config->prefix[MAXPATHLEN] = L'\0';
+    joinpath(config->prefix, L"Modules/Setup");
+    if (isfile(config->prefix)) {
         /* Check VPATH to see if argv0_path is in the build directory. */
         vpath = Py_DecodeLocale(VPATH, NULL);
         if (vpath != NULL) {
-            wcsncpy(prefix, argv0_path, MAXPATHLEN);
-            prefix[MAXPATHLEN] = L'\0';
-            joinpath(prefix, vpath);
+            wcsncpy(config->prefix, calculate->argv0_path, MAXPATHLEN);
+            config->prefix[MAXPATHLEN] = L'\0';
+            joinpath(config->prefix, vpath);
             PyMem_RawFree(vpath);
-            joinpath(prefix, L"Lib");
-            joinpath(prefix, LANDMARK);
-            if (ismodule(prefix))
+            joinpath(config->prefix, L"Lib");
+            joinpath(config->prefix, LANDMARK);
+            if (ismodule(config->prefix)) {
                 return -1;
+            }
         }
     }
 
     /* Search from argv0_path, until root is found */
-    copy_absolute(prefix, argv0_path, MAXPATHLEN+1);
+    copy_absolute(config->prefix, calculate->argv0_path, MAXPATHLEN+1);
     do {
-        n = wcslen(prefix);
-        joinpath(prefix, lib_python);
-        joinpath(prefix, LANDMARK);
-        if (ismodule(prefix))
+        n = wcslen(config->prefix);
+        joinpath(config->prefix, calculate->lib_python);
+        joinpath(config->prefix, LANDMARK);
+        if (ismodule(config->prefix)) {
             return 1;
-        prefix[n] = L'\0';
-        reduce(prefix);
-    } while (prefix[0]);
+        }
+        config->prefix[n] = L'\0';
+        reduce(config->prefix);
+    } while (config->prefix[0]);
 
     /* Look at configure's PREFIX */
-    wcsncpy(prefix, _prefix, MAXPATHLEN);
-    prefix[MAXPATHLEN] = L'\0';
-    joinpath(prefix, lib_python);
-    joinpath(prefix, LANDMARK);
-    if (ismodule(prefix))
+    wcsncpy(config->prefix, calculate->prefix, MAXPATHLEN);
+    config->prefix[MAXPATHLEN] = L'\0';
+    joinpath(config->prefix, calculate->lib_python);
+    joinpath(config->prefix, LANDMARK);
+    if (ismodule(config->prefix)) {
         return 1;
+    }
 
     /* Fail */
     return 0;
 }
 
 
+static void
+calculate_prefix(PyCalculatePath *calculate, PyPathConfig *config)
+{
+    calculate->prefix_found = search_for_prefix(calculate, config);
+    if (!calculate->prefix_found) {
+        if (!Py_FrozenFlag) {
+            fprintf(stderr,
+                "Could not find platform independent libraries <prefix>\n");
+        }
+        wcsncpy(config->prefix, calculate->prefix, MAXPATHLEN);
+        joinpath(config->prefix, calculate->lib_python);
+    }
+    else {
+        reduce(config->prefix);
+    }
+}
+
+
+static void
+calculate_reduce_prefix(PyCalculatePath *calculate, PyPathConfig *config)
+{
+    /* Reduce prefix and exec_prefix to their essence,
+     * e.g. /usr/local/lib/python1.5 is reduced to /usr/local.
+     * If we're loading relative to the build directory,
+     * return the compiled-in defaults instead.
+     */
+    if (calculate->prefix_found > 0) {
+        reduce(config->prefix);
+        reduce(config->prefix);
+        /* The prefix is the root directory, but reduce() chopped
+         * off the "/". */
+        if (!config->prefix[0]) {
+            wcscpy(config->prefix, separator);
+        }
+    }
+    else {
+        wcsncpy(config->prefix, calculate->prefix, MAXPATHLEN);
+    }
+}
+
+
 /* search_for_exec_prefix requires that argv0_path be no more than
    MAXPATHLEN bytes long.
 */
 static int
-search_for_exec_prefix(wchar_t *argv0_path, wchar_t *home,
-                       wchar_t *_exec_prefix, wchar_t *lib_python)
+search_for_exec_prefix(PyCalculatePath *calculate, PyPathConfig *config)
 {
     size_t n;
 
     /* If PYTHONHOME is set, we believe it unconditionally */
-    if (home) {
-        wchar_t *delim;
-        delim = wcschr(home, DELIM);
-        if (delim)
-            wcsncpy(exec_prefix, delim+1, MAXPATHLEN);
-        else
-            wcsncpy(exec_prefix, home, MAXPATHLEN);
-        exec_prefix[MAXPATHLEN] = L'\0';
-        joinpath(exec_prefix, lib_python);
-        joinpath(exec_prefix, L"lib-dynload");
+    if (calculate->home) {
+        wchar_t *delim = wcschr(calculate->home, DELIM);
+        if (delim) {
+            wcsncpy(config->exec_prefix, delim+1, MAXPATHLEN);
+        }
+        else {
+            wcsncpy(config->exec_prefix, calculate->home, MAXPATHLEN);
+        }
+        config->exec_prefix[MAXPATHLEN] = L'\0';
+        joinpath(config->exec_prefix, calculate->lib_python);
+        joinpath(config->exec_prefix, L"lib-dynload");
         return 1;
     }
 
     /* Check to see if argv[0] is in the build directory. "pybuilddir.txt"
        is written by setup.py and contains the relative path to the location
        of shared library modules. */
-    wcsncpy(exec_prefix, argv0_path, MAXPATHLEN);
-    exec_prefix[MAXPATHLEN] = L'\0';
-    joinpath(exec_prefix, L"pybuilddir.txt");
-    if (isfile(exec_prefix)) {
-        FILE *f = _Py_wfopen(exec_prefix, L"rb");
-        if (f == NULL)
+    wcsncpy(config->exec_prefix, calculate->argv0_path, MAXPATHLEN);
+    config->exec_prefix[MAXPATHLEN] = L'\0';
+    joinpath(config->exec_prefix, L"pybuilddir.txt");
+    if (isfile(config->exec_prefix)) {
+        FILE *f = _Py_wfopen(config->exec_prefix, L"rb");
+        if (f == NULL) {
             errno = 0;
+        }
         else {
             char buf[MAXPATHLEN+1];
             PyObject *decoded;
@@ -422,9 +517,9 @@ search_for_exec_prefix(wchar_t *argv0_path, wchar_t *home,
                 Py_DECREF(decoded);
                 if (k >= 0) {
                     rel_builddir_path[k] = L'\0';
-                    wcsncpy(exec_prefix, argv0_path, MAXPATHLEN);
-                    exec_prefix[MAXPATHLEN] = L'\0';
-                    joinpath(exec_prefix, rel_builddir_path);
+                    wcsncpy(config->exec_prefix, calculate->argv0_path, MAXPATHLEN);
+                    config->exec_prefix[MAXPATHLEN] = L'\0';
+                    joinpath(config->exec_prefix, rel_builddir_path);
                     return -1;
                 }
             }
@@ -432,87 +527,85 @@ search_for_exec_prefix(wchar_t *argv0_path, wchar_t *home,
     }
 
     /* Search from argv0_path, until root is found */
-    copy_absolute(exec_prefix, argv0_path, MAXPATHLEN+1);
+    copy_absolute(config->exec_prefix, calculate->argv0_path, MAXPATHLEN+1);
     do {
-        n = wcslen(exec_prefix);
-        joinpath(exec_prefix, lib_python);
-        joinpath(exec_prefix, L"lib-dynload");
-        if (isdir(exec_prefix))
+        n = wcslen(config->exec_prefix);
+        joinpath(config->exec_prefix, calculate->lib_python);
+        joinpath(config->exec_prefix, L"lib-dynload");
+        if (isdir(config->exec_prefix)) {
             return 1;
-        exec_prefix[n] = L'\0';
-        reduce(exec_prefix);
-    } while (exec_prefix[0]);
+        }
+        config->exec_prefix[n] = L'\0';
+        reduce(config->exec_prefix);
+    } while (config->exec_prefix[0]);
 
     /* Look at configure's EXEC_PREFIX */
-    wcsncpy(exec_prefix, _exec_prefix, MAXPATHLEN);
-    exec_prefix[MAXPATHLEN] = L'\0';
-    joinpath(exec_prefix, lib_python);
-    joinpath(exec_prefix, L"lib-dynload");
-    if (isdir(exec_prefix))
+    wcsncpy(config->exec_prefix, calculate->exec_prefix, MAXPATHLEN);
+    config->exec_prefix[MAXPATHLEN] = L'\0';
+    joinpath(config->exec_prefix, calculate->lib_python);
+    joinpath(config->exec_prefix, L"lib-dynload");
+    if (isdir(config->exec_prefix)) {
         return 1;
+    }
 
     /* Fail */
     return 0;
 }
 
+
 static void
-calculate_path(const _PyMainInterpreterConfig *config)
+calculate_exec_prefix(PyCalculatePath *calculate, PyPathConfig *config)
 {
-    extern wchar_t *Py_GetProgramName(void);
-
-    static const wchar_t delimiter[2] = {DELIM, '\0'};
-    static const wchar_t separator[2] = {SEP, '\0'};
-    wchar_t *home = _Py_GetPythonHomeWithConfig(config);
-    char *_path = getenv("PATH");
-    wchar_t *path_buffer = NULL;
-    wchar_t *path = NULL;
-    wchar_t *prog = Py_GetProgramName();
-    wchar_t argv0_path[MAXPATHLEN+1];
-    wchar_t zip_path[MAXPATHLEN+1];
-    int pfound, efound; /* 1 if found; -1 if found build directory */
-    wchar_t *buf;
-    size_t bufsz;
-    size_t prefixsz;
-    wchar_t *defpath;
-#ifdef WITH_NEXT_FRAMEWORK
-    NSModule pythonModule;
-    const char*    modPath;
-#endif
-#ifdef __APPLE__
-#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4
-    uint32_t nsexeclength = MAXPATHLEN;
-#else
-    unsigned long nsexeclength = MAXPATHLEN;
-#endif
-    char execpath[MAXPATHLEN+1];
-#endif
-    wchar_t *_pythonpath, *_prefix, *_exec_prefix;
-    wchar_t *lib_python;
+    calculate->exec_prefix_found = search_for_exec_prefix(calculate, config);
+    if (!calculate->exec_prefix_found) {
+        if (!Py_FrozenFlag) {
+            fprintf(stderr,
+                "Could not find platform dependent libraries <exec_prefix>\n");
+        }
+        wcsncpy(config->exec_prefix, calculate->exec_prefix, MAXPATHLEN);
+        joinpath(config->exec_prefix, L"lib/lib-dynload");
+    }
+    /* If we found EXEC_PREFIX do *not* reduce it!  (Yet.) */
+}
 
-    _pythonpath = Py_DecodeLocale(PYTHONPATH, NULL);
-    _prefix = Py_DecodeLocale(PREFIX, NULL);
-    _exec_prefix = Py_DecodeLocale(EXEC_PREFIX, NULL);
-    lib_python = Py_DecodeLocale("lib/python" VERSION, NULL);
 
-    if (!_pythonpath || !_prefix || !_exec_prefix || !lib_python) {
-        Py_FatalError(
-            "Unable to decode path variables in getpath.c: "
-            "memory error");
+static void
+calculate_reduce_exec_prefix(PyCalculatePath *calculate, PyPathConfig *config)
+{
+    if (calculate->exec_prefix_found > 0) {
+        reduce(config->exec_prefix);
+        reduce(config->exec_prefix);
+        reduce(config->exec_prefix);
+        if (!config->exec_prefix[0]) {
+            wcscpy(config->exec_prefix, separator);
+        }
     }
-
-    if (_path) {
-        path_buffer = Py_DecodeLocale(_path, NULL);
-        path = path_buffer;
+    else {
+        wcsncpy(config->exec_prefix, calculate->exec_prefix, MAXPATHLEN);
     }
+}
+
 
+static void
+calculate_progpath(PyCalculatePath *calculate, PyPathConfig *config)
+{
     /* If there is no slash in the argv0 path, then we have to
      * assume python is on the user's $PATH, since there's no
      * other way to find a directory to start the search from.  If
      * $PATH isn't exported, you lose.
      */
-    if (wcschr(prog, SEP))
-        wcsncpy(progpath, prog, MAXPATHLEN);
+    if (wcschr(calculate->prog, SEP)) {
+        wcsncpy(config->progpath, calculate->prog, MAXPATHLEN);
+    }
+
 #ifdef __APPLE__
+#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4
+    uint32_t nsexeclength = MAXPATHLEN;
+#else
+    unsigned long nsexeclength = MAXPATHLEN;
+#endif
+    char execpath[MAXPATHLEN+1];
+
      /* On Mac OS X, if a script uses an interpreter of the form
       * "#!/opt/python2.3/bin/python", the kernel only passes "python"
       * as argv[0], which falls through to the $PATH search below.
@@ -524,47 +617,60 @@ calculate_path(const _PyMainInterpreterConfig *config)
       * absolutize() should help us out below
       */
     else if(0 == _NSGetExecutablePath(execpath, &nsexeclength) && execpath[0] == SEP) {
-        size_t r = mbstowcs(progpath, execpath, MAXPATHLEN+1);
+        size_t r = mbstowcs(config->progpath, execpath, MAXPATHLEN+1);
         if (r == (size_t)-1 || r > MAXPATHLEN) {
             /* Could not convert execpath, or it's too long. */
-            progpath[0] = '\0';
+            config->progpath[0] = '\0';
         }
     }
 #endif /* __APPLE__ */
-    else if (path) {
+    else if (calculate->path_env) {
+        wchar_t *path = calculate->path_env;
         while (1) {
             wchar_t *delim = wcschr(path, DELIM);
 
             if (delim) {
                 size_t len = delim - path;
-                if (len > MAXPATHLEN)
+                if (len > MAXPATHLEN) {
                     len = MAXPATHLEN;
-                wcsncpy(progpath, path, len);
-                *(progpath + len) = '\0';
+                }
+                wcsncpy(config->progpath, path, len);
+                *(config->progpath + len) = '\0';
+            }
+            else {
+                wcsncpy(config->progpath, path, MAXPATHLEN);
             }
-            else
-                wcsncpy(progpath, path, MAXPATHLEN);
 
-            joinpath(progpath, prog);
-            if (isxfile(progpath))
+            joinpath(config->progpath, calculate->prog);
+            if (isxfile(config->progpath)) {
                 break;
+            }
 
             if (!delim) {
-                progpath[0] = L'\0';
+                config->progpath[0] = L'\0';
                 break;
             }
             path = delim + 1;
         }
     }
-    else
-        progpath[0] = '\0';
-    PyMem_RawFree(path_buffer);
-    if (progpath[0] != SEP && progpath[0] != '\0')
-        absolutize(progpath);
-    wcsncpy(argv0_path, progpath, MAXPATHLEN);
-    argv0_path[MAXPATHLEN] = '\0';
+    else {
+        config->progpath[0] = '\0';
+    }
+    if (config->progpath[0] != SEP && config->progpath[0] != '\0') {
+        absolutize(config->progpath);
+    }
+}
+
+
+static void
+calculate_argv0_path(PyCalculatePath *calculate, PyPathConfig *config)
+{
+    wcsncpy(calculate->argv0_path, config->progpath, MAXPATHLEN);
+    calculate->argv0_path[MAXPATHLEN] = '\0';
 
 #ifdef WITH_NEXT_FRAMEWORK
+    NSModule pythonModule;
+
     /* On Mac OS X we have a special case if we're running from a framework.
     ** This is because the python home should be set relative to the library,
     ** which is in the framework, not relative to the executable, which may
@@ -572,7 +678,7 @@ calculate_path(const _PyMainInterpreterConfig *config)
     */
     pythonModule = NSModuleForSymbol(NSLookupAndBindSymbol("_Py_Initialize"));
     /* Use dylib functions to find out where the framework was loaded from */
-    modPath = NSLibraryNameForModule(pythonModule);
+    const char* modPath = NSLibraryNameForModule(pythonModule);
     if (modPath != NULL) {
         /* We're in a framework. */
         /* See if we might be in the build directory. The framework in the
@@ -587,153 +693,132 @@ calculate_path(const _PyMainInterpreterConfig *config)
             Py_FatalError("Cannot decode framework location");
         }
 
-        wcsncpy(argv0_path, wbuf, MAXPATHLEN);
-        reduce(argv0_path);
-        joinpath(argv0_path, lib_python);
-        joinpath(argv0_path, LANDMARK);
-        if (!ismodule(argv0_path)) {
+        wcsncpy(calculate->argv0_path, wbuf, MAXPATHLEN);
+        reduce(calculate->argv0_path);
+        joinpath(calculate->argv0_path, calculate->lib_python);
+        joinpath(calculate->argv0_path, LANDMARK);
+        if (!ismodule(calculate->argv0_path)) {
             /* We are in the build directory so use the name of the
                executable - we know that the absolute path is passed */
-            wcsncpy(argv0_path, progpath, MAXPATHLEN);
+            wcsncpy(calculate->argv0_path, config->progpath, MAXPATHLEN);
         }
         else {
             /* Use the location of the library as the progpath */
-            wcsncpy(argv0_path, wbuf, MAXPATHLEN);
+            wcsncpy(calculate->argv0_path, wbuf, MAXPATHLEN);
         }
         PyMem_RawFree(wbuf);
     }
 #endif
 
 #if HAVE_READLINK
-    {
-        wchar_t tmpbuffer[MAXPATHLEN+1];
-        int linklen = _Py_wreadlink(progpath, tmpbuffer, MAXPATHLEN);
-        while (linklen != -1) {
-            if (tmpbuffer[0] == SEP)
-                /* tmpbuffer should never be longer than MAXPATHLEN,
-                   but extra check does not hurt */
-                wcsncpy(argv0_path, tmpbuffer, MAXPATHLEN);
-            else {
-                /* Interpret relative to progpath */
-                reduce(argv0_path);
-                joinpath(argv0_path, tmpbuffer);
-            }
-            linklen = _Py_wreadlink(argv0_path, tmpbuffer, MAXPATHLEN);
+    wchar_t tmpbuffer[MAXPATHLEN+1];
+    int linklen = _Py_wreadlink(config->progpath, tmpbuffer, MAXPATHLEN);
+    while (linklen != -1) {
+        if (tmpbuffer[0] == SEP) {
+            /* tmpbuffer should never be longer than MAXPATHLEN,
+               but extra check does not hurt */
+            wcsncpy(calculate->argv0_path, tmpbuffer, MAXPATHLEN);
+        }
+        else {
+            /* Interpret relative to progpath */
+            reduce(calculate->argv0_path);
+            joinpath(calculate->argv0_path, tmpbuffer);
         }
+        linklen = _Py_wreadlink(calculate->argv0_path, tmpbuffer, MAXPATHLEN);
     }
 #endif /* HAVE_READLINK */
 
-    reduce(argv0_path);
+    reduce(calculate->argv0_path);
     /* At this point, argv0_path is guaranteed to be less than
-       MAXPATHLEN bytes long.
-    */
+       MAXPATHLEN bytes long. */
+}
 
-    /* Search for an environment configuration file, first in the
-       executable's directory and then in the parent directory.
-       If found, open it for use when searching for prefixes.
-    */
 
-    {
-        wchar_t tmpbuffer[MAXPATHLEN+1];
-        wchar_t *env_cfg = L"pyvenv.cfg";
-        FILE * env_file = NULL;
+/* Search for an "pyvenv.cfg" environment configuration file, first in the
+   executable's directory and then in the parent directory.
+   If found, open it for use when searching for prefixes.
+*/
+static void
+calculate_read_pyenv(PyCalculatePath *calculate)
+{
+    wchar_t tmpbuffer[MAXPATHLEN+1];
+    wchar_t *env_cfg = L"pyvenv.cfg";
+    FILE *env_file;
+
+    wcscpy(tmpbuffer, calculate->argv0_path);
 
-        wcscpy(tmpbuffer, argv0_path);
+    joinpath(tmpbuffer, env_cfg);
+    env_file = _Py_wfopen(tmpbuffer, L"r");
+    if (env_file == NULL) {
+        errno = 0;
 
+        reduce(tmpbuffer);
+        reduce(tmpbuffer);
         joinpath(tmpbuffer, env_cfg);
+
         env_file = _Py_wfopen(tmpbuffer, L"r");
         if (env_file == NULL) {
             errno = 0;
-            reduce(tmpbuffer);
-            reduce(tmpbuffer);
-            joinpath(tmpbuffer, env_cfg);
-            env_file = _Py_wfopen(tmpbuffer, L"r");
-            if (env_file == NULL) {
-                errno = 0;
-            }
-        }
-        if (env_file != NULL) {
-            /* Look for a 'home' variable and set argv0_path to it, if found */
-            if (find_env_config_value(env_file, L"home", tmpbuffer)) {
-                wcscpy(argv0_path, tmpbuffer);
-            }
-            fclose(env_file);
-            env_file = NULL;
         }
     }
 
-    pfound = search_for_prefix(argv0_path, home, _prefix, lib_python);
-    if (!pfound) {
-        if (!Py_FrozenFlag)
-            fprintf(stderr,
-                "Could not find platform independent libraries <prefix>\n");
-        wcsncpy(prefix, _prefix, MAXPATHLEN);
-        joinpath(prefix, lib_python);
-    }
-    else
-        reduce(prefix);
-
-    wcsncpy(zip_path, prefix, MAXPATHLEN);
-    zip_path[MAXPATHLEN] = L'\0';
-    if (pfound > 0) { /* Use the reduced prefix returned by Py_GetPrefix() */
-        reduce(zip_path);
-        reduce(zip_path);
-    }
-    else
-        wcsncpy(zip_path, _prefix, MAXPATHLEN);
-    joinpath(zip_path, L"lib/python00.zip");
-    bufsz = wcslen(zip_path);   /* Replace "00" with version */
-    zip_path[bufsz - 6] = VERSION[0];
-    zip_path[bufsz - 5] = VERSION[2];
-
-    efound = search_for_exec_prefix(argv0_path, home,
-                                    _exec_prefix, lib_python);
-    if (!efound) {
-        if (!Py_FrozenFlag)
-            fprintf(stderr,
-                "Could not find platform dependent libraries <exec_prefix>\n");
-        wcsncpy(exec_prefix, _exec_prefix, MAXPATHLEN);
-        joinpath(exec_prefix, L"lib/lib-dynload");
+    if (env_file == NULL) {
+        return;
     }
-    /* If we found EXEC_PREFIX do *not* reduce it!  (Yet.) */
 
-    if ((!pfound || !efound) && !Py_FrozenFlag)
-        fprintf(stderr,
-                "Consider setting $PYTHONHOME to <prefix>[:<exec_prefix>]\n");
+    /* Look for a 'home' variable and set argv0_path to it, if found */
+    if (find_env_config_value(env_file, L"home", tmpbuffer)) {
+        wcscpy(calculate->argv0_path, tmpbuffer);
+    }
+    fclose(env_file);
+}
 
-    /* Calculate size of return buffer.
-     */
-    bufsz = 0;
 
-    wchar_t *env_path = NULL;
-    if (config) {
-        if (config->module_search_path_env) {
-            bufsz += wcslen(config->module_search_path_env) + 1;
-        }
+static void
+calculate_zip_path(PyCalculatePath *calculate, PyPathConfig *config)
+{
+    wcsncpy(calculate->zip_path, config->prefix, MAXPATHLEN);
+    calculate->zip_path[MAXPATHLEN] = L'\0';
+
+    if (calculate->prefix_found > 0) {
+        /* Use the reduced prefix returned by Py_GetPrefix() */
+        reduce(calculate->zip_path);
+        reduce(calculate->zip_path);
     }
     else {
-        char *env_pathb = Py_GETENV("PYTHONPATH");
-        if (env_pathb && env_pathb[0] != '\0') {
-            size_t env_path_len;
-            env_path = Py_DecodeLocale(env_pathb, &env_path_len);
-            /* FIXME: handle decoding and memory error */
-            if (env_path != NULL) {
-                bufsz += env_path_len + 1;
-            }
-        }
+        wcsncpy(calculate->zip_path, calculate->prefix, MAXPATHLEN);
     }
+    joinpath(calculate->zip_path, L"lib/python00.zip");
+
+    /* Replace "00" with version */
+    size_t bufsz = wcslen(calculate->zip_path);
+    calculate->zip_path[bufsz - 6] = VERSION[0];
+    calculate->zip_path[bufsz - 5] = VERSION[2];
+}
 
-    defpath = _pythonpath;
-    prefixsz = wcslen(prefix) + 1;
+
+static wchar_t *
+calculate_module_search_path(PyCalculatePath *calculate, PyPathConfig *config)
+{
+    /* Calculate size of return buffer */
+    size_t bufsz = 0;
+    if (calculate->module_search_path_env != NULL) {
+        bufsz += wcslen(calculate->module_search_path_env) + 1;
+    }
+
+    wchar_t *defpath = calculate->pythonpath;
+    size_t prefixsz = wcslen(config->prefix) + 1;
     while (1) {
         wchar_t *delim = wcschr(defpath, DELIM);
 
-        if (defpath[0] != SEP)
+        if (defpath[0] != SEP) {
             /* Paths are relative to prefix */
             bufsz += prefixsz;
+        }
 
-        if (delim)
+        if (delim) {
             bufsz += delim - defpath + 1;
+        }
         else {
             bufsz += wcslen(defpath) + 1;
             break;
@@ -741,46 +826,40 @@ calculate_path(const _PyMainInterpreterConfig *config)
         defpath = delim + 1;
     }
 
-    bufsz += wcslen(zip_path) + 1;
-    bufsz += wcslen(exec_prefix) + 1;
+    bufsz += wcslen(calculate->zip_path) + 1;
+    bufsz += wcslen(config->exec_prefix) + 1;
 
-    buf = PyMem_RawMalloc(bufsz * sizeof(wchar_t));
+    /* Allocate the buffer */
+    wchar_t *buf = PyMem_RawMalloc(bufsz * sizeof(wchar_t));
     if (buf == NULL) {
         Py_FatalError(
             "Not enough memory for dynamic PYTHONPATH");
     }
+    buf[0] = '\0';
 
     /* Run-time value of $PYTHONPATH goes first */
-    buf[0] = '\0';
-    if (config) {
-        if (config->module_search_path_env) {
-            wcscpy(buf, config->module_search_path_env);
-            wcscat(buf, delimiter);
-        }
-    }
-    else {
-        if (env_path) {
-            wcscpy(buf, env_path);
-            wcscat(buf, delimiter);
-        }
+    if (calculate->module_search_path_env) {
+        wcscpy(buf, calculate->module_search_path_env);
+        wcscat(buf, delimiter);
     }
-    PyMem_RawFree(env_path);
 
     /* Next is the default zip path */
-    wcscat(buf, zip_path);
+    wcscat(buf, calculate->zip_path);
     wcscat(buf, delimiter);
 
     /* Next goes merge of compile-time $PYTHONPATH with
      * dynamically located prefix.
      */
-    defpath = _pythonpath;
+    defpath = calculate->pythonpath;
     while (1) {
         wchar_t *delim = wcschr(defpath, DELIM);
 
         if (defpath[0] != SEP) {
-            wcscat(buf, prefix);
-            if (prefixsz >= 2 && prefix[prefixsz - 2] != SEP &&
-                defpath[0] != (delim ? DELIM : L'\0')) {  /* not empty */
+            wcscat(buf, config->prefix);
+            if (prefixsz >= 2 && config->prefix[prefixsz - 2] != SEP &&
+                defpath[0] != (delim ? DELIM : L'\0'))
+            {
+                /* not empty */
                 wcscat(buf, separator);
             }
         }
@@ -800,41 +879,130 @@ calculate_path(const _PyMainInterpreterConfig *config)
     wcscat(buf, delimiter);
 
     /* Finally, on goes the directory for dynamic-load modules */
-    wcscat(buf, exec_prefix);
+    wcscat(buf, config->exec_prefix);
 
-    /* And publish the results */
-    module_search_path = buf;
+    return buf;
+}
 
-    /* Reduce prefix and exec_prefix to their essence,
-     * e.g. /usr/local/lib/python1.5 is reduced to /usr/local.
-     * If we're loading relative to the build directory,
-     * return the compiled-in defaults instead.
-     */
-    if (pfound > 0) {
-        reduce(prefix);
-        reduce(prefix);
-        /* The prefix is the root directory, but reduce() chopped
-         * off the "/". */
-        if (!prefix[0])
-                wcscpy(prefix, separator);
+
+#define DECODE_FAILED(NAME, LEN) \
+    ((LEN) == (size_t)-2) \
+     ? _Py_INIT_ERR("failed to decode " #NAME) \
+     : _Py_INIT_NO_MEMORY()
+
+
+static _PyInitError
+calculate_init(PyCalculatePath *calculate,
+               const _PyMainInterpreterConfig *main_config)
+{
+    _PyInitError err;
+
+    err = _Py_GetPythonHomeWithConfig(main_config, &calculate->home);
+    if (_Py_INIT_FAILED(err)) {
+        return err;
+    }
+
+    size_t len;
+    char *path = getenv("PATH");
+    if (path) {
+        calculate->path_env = Py_DecodeLocale(path, &len);
+        if (!calculate->path_env) {
+            return DECODE_FAILED("PATH environment variable", len);
+        }
+    }
+
+    calculate->prog = Py_GetProgramName();
+
+    calculate->pythonpath = Py_DecodeLocale(PYTHONPATH, &len);
+    if (!calculate->pythonpath) {
+        return DECODE_FAILED("PYTHONPATH define", len);
+    }
+    calculate->prefix = Py_DecodeLocale(PREFIX, &len);
+    if (!calculate->prefix) {
+        return DECODE_FAILED("PREFIX define", len);
+    }
+    calculate->exec_prefix = Py_DecodeLocale(EXEC_PREFIX, &len);
+    if (!calculate->prefix) {
+        return DECODE_FAILED("EXEC_PREFIX define", len);
+    }
+    calculate->lib_python = Py_DecodeLocale("lib/python" VERSION, &len);
+    if (!calculate->lib_python) {
+        return DECODE_FAILED("EXEC_PREFIX define", len);
     }
-    else
-        wcsncpy(prefix, _prefix, MAXPATHLEN);
 
-    if (efound > 0) {
-        reduce(exec_prefix);
-        reduce(exec_prefix);
-        reduce(exec_prefix);
-        if (!exec_prefix[0])
-                wcscpy(exec_prefix, separator);
+    calculate->module_search_path_env = NULL;
+    if (main_config) {
+        if (main_config->module_search_path_env) {
+            calculate->module_search_path_env = main_config->module_search_path_env;
+        }
+
+    }
+    else {
+        char *pythonpath = Py_GETENV("PYTHONPATH");
+        if (pythonpath && pythonpath[0] != '\0') {
+            calculate->module_search_path_buffer = Py_DecodeLocale(pythonpath, &len);
+            if (!calculate->module_search_path_buffer) {
+                return DECODE_FAILED("PYTHONPATH environment variable", len);
+            }
+            calculate->module_search_path_env = calculate->module_search_path_buffer;
+        }
     }
-    else
-        wcsncpy(exec_prefix, _exec_prefix, MAXPATHLEN);
+    return _Py_INIT_OK();
+}
+
 
-    PyMem_RawFree(_pythonpath);
-    PyMem_RawFree(_prefix);
-    PyMem_RawFree(_exec_prefix);
-    PyMem_RawFree(lib_python);
+static void
+calculate_free(PyCalculatePath *calculate)
+{
+    PyMem_RawFree(calculate->pythonpath);
+    PyMem_RawFree(calculate->prefix);
+    PyMem_RawFree(calculate->exec_prefix);
+    PyMem_RawFree(calculate->lib_python);
+    PyMem_RawFree(calculate->path_env);
+    PyMem_RawFree(calculate->module_search_path_buffer);
+}
+
+
+static void
+calculate_path_impl(PyCalculatePath *calculate, PyPathConfig *config)
+{
+    calculate_progpath(calculate, config);
+    calculate_argv0_path(calculate, config);
+    calculate_read_pyenv(calculate);
+    calculate_prefix(calculate, config);
+    calculate_zip_path(calculate, config);
+    calculate_exec_prefix(calculate, config);
+
+    if ((!calculate->prefix_found || !calculate->exec_prefix_found) && !Py_FrozenFlag) {
+        fprintf(stderr,
+                "Consider setting $PYTHONHOME to <prefix>[:<exec_prefix>]\n");
+    }
+
+    config->module_search_path = calculate_module_search_path(calculate, config);
+    calculate_reduce_prefix(calculate, config);
+    calculate_reduce_exec_prefix(calculate, config);
+}
+
+
+static void
+calculate_path(const _PyMainInterpreterConfig *main_config)
+{
+    PyCalculatePath calculate;
+    memset(&calculate, 0, sizeof(calculate));
+
+    _PyInitError err = calculate_init(&calculate, main_config);
+    if (_Py_INIT_FAILED(err)) {
+        calculate_free(&calculate);
+        _Py_FatalInitError(err);
+    }
+
+    PyPathConfig new_path_config;
+    memset(&new_path_config, 0, sizeof(new_path_config));
+
+    calculate_path_impl(&calculate, &new_path_config);
+    path_config = new_path_config;
+
+    calculate_free(&calculate);
 }
 
 
@@ -842,63 +1010,74 @@ calculate_path(const _PyMainInterpreterConfig *config)
 void
 Py_SetPath(const wchar_t *path)
 {
-    if (module_search_path != NULL) {
-        PyMem_RawFree(module_search_path);
-        module_search_path = NULL;
+    if (path_config.module_search_path != NULL) {
+        PyMem_RawFree(path_config.module_search_path);
+        path_config.module_search_path = NULL;
     }
-    if (path != NULL) {
-        extern wchar_t *Py_GetProgramName(void);
-        wchar_t *prog = Py_GetProgramName();
-        wcsncpy(progpath, prog, MAXPATHLEN);
-        exec_prefix[0] = prefix[0] = L'\0';
-        module_search_path = PyMem_RawMalloc((wcslen(path) + 1) * sizeof(wchar_t));
-        if (module_search_path != NULL)
-            wcscpy(module_search_path, path);
+
+    if (path == NULL) {
+        return;
+    }
+
+    wchar_t *prog = Py_GetProgramName();
+    wcsncpy(path_config.progpath, prog, MAXPATHLEN);
+    path_config.exec_prefix[0] = path_config.prefix[0] = L'\0';
+    path_config.module_search_path = PyMem_RawMalloc((wcslen(path) + 1) * sizeof(wchar_t));
+    if (path_config.module_search_path != NULL) {
+        wcscpy(path_config.module_search_path, path);
     }
 }
 
+
 wchar_t *
-_Py_GetPathWithConfig(const _PyMainInterpreterConfig *config)
+_Py_GetPathWithConfig(const _PyMainInterpreterConfig *main_config)
 {
-    if (!module_search_path) {
-        calculate_path(config);
+    if (!path_config.module_search_path) {
+        calculate_path(main_config);
     }
-    return module_search_path;
+    return path_config.module_search_path;
 }
 
+
 wchar_t *
 Py_GetPath(void)
 {
-    if (!module_search_path)
+    if (!path_config.module_search_path) {
         calculate_path(NULL);
-    return module_search_path;
+    }
+    return path_config.module_search_path;
 }
 
+
 wchar_t *
 Py_GetPrefix(void)
 {
-    if (!module_search_path)
+    if (!path_config.module_search_path) {
         calculate_path(NULL);
-    return prefix;
+    }
+    return path_config.prefix;
 }
 
+
 wchar_t *
 Py_GetExecPrefix(void)
 {
-    if (!module_search_path)
+    if (!path_config.module_search_path) {
         calculate_path(NULL);
-    return exec_prefix;
+    }
+    return path_config.exec_prefix;
 }
 
+
 wchar_t *
 Py_GetProgramFullPath(void)
 {
-    if (!module_search_path)
+    if (!path_config.module_search_path) {
         calculate_path(NULL);
-    return progpath;
+    }
+    return path_config.progpath;
 }
 
-
 #ifdef __cplusplus
 }
 #endif
diff --git a/Modules/main.c b/Modules/main.c
index 07e0d2aa85c..349d8c3f323 100644
--- a/Modules/main.c
+++ b/Modules/main.c
@@ -36,6 +36,16 @@
 extern "C" {
 #endif
 
+#define SET_DECODE_ERROR(NAME, LEN) \
+    do { \
+        if ((LEN) == (size_t)-2) { \
+            pymain->err = _Py_INIT_ERR("failed to decode " #NAME); \
+        } \
+        else { \
+            pymain->err = _Py_INIT_NO_MEMORY(); \
+        } \
+    } while (0)
+
 /* For Py_GetArgcArgv(); set by main() */
 static wchar_t **orig_argv;
 static int orig_argc;
@@ -417,9 +427,6 @@ typedef struct {
      .env_warning_options = {0, NULL}}
 
 
-#define INIT_NO_MEMORY() _Py_INIT_ERR("memory allocation failed")
-
-
 static void
 pymain_optlist_clear(_Py_OptList *list)
 {
@@ -510,14 +517,14 @@ pymain_wstrdup(_PyMain *pymain, wchar_t *str)
 {
     size_t len = wcslen(str) + 1;  /* +1 for NUL character */
     if (len > (size_t)PY_SSIZE_T_MAX / sizeof(wchar_t)) {
-        pymain->err = INIT_NO_MEMORY();
+        pymain->err = _Py_INIT_NO_MEMORY();
         return NULL;
     }
 
     size_t size = len * sizeof(wchar_t);
     wchar_t *str2 = PyMem_RawMalloc(size);
     if (str2 == NULL) {
-        pymain->err = INIT_NO_MEMORY();
+        pymain->err = _Py_INIT_NO_MEMORY();
         return NULL;
     }
 
@@ -538,7 +545,7 @@ pymain_optlist_append(_PyMain *pymain, _Py_OptList *list, wchar_t *str)
     wchar_t **options2 = (wchar_t **)PyMem_RawRealloc(list->options, size);
     if (options2 == NULL) {
         PyMem_RawFree(str2);
-        pymain->err = INIT_NO_MEMORY();
+        pymain->err = _Py_INIT_NO_MEMORY();
         return -1;
     }
     options2[list->len] = str2;
@@ -571,7 +578,7 @@ pymain_parse_cmdline_impl(_PyMain *pymain)
             size_t len = wcslen(_PyOS_optarg) + 1 + 1;
             wchar_t *command = PyMem_RawMalloc(sizeof(wchar_t) * len);
             if (command == NULL) {
-                pymain->err = INIT_NO_MEMORY();
+                pymain->err = _Py_INIT_NO_MEMORY();
                 return -1;
             }
             memcpy(command, _PyOS_optarg, len * sizeof(wchar_t));
@@ -717,7 +724,7 @@ pymain_add_xoptions(_PyMain *pymain)
     for (size_t i=0; i < options->len; i++) {
         wchar_t *option = options->options[i];
         if (_PySys_AddXOptionWithError(option) < 0) {
-            pymain->err = INIT_NO_MEMORY();
+            pymain->err = _Py_INIT_NO_MEMORY();
             return -1;
         }
     }
@@ -748,11 +755,11 @@ pymain_add_warnings_options(_PyMain *pymain)
     PySys_ResetWarnOptions();
 
     if (pymain_add_warnings_optlist(&pymain->env_warning_options) < 0) {
-        pymain->err = INIT_NO_MEMORY();
+        pymain->err = _Py_INIT_NO_MEMORY();
         return -1;
     }
     if (pymain_add_warnings_optlist(&pymain->cmdline.warning_options) < 0) {
-        pymain->err = INIT_NO_MEMORY();
+        pymain->err = _Py_INIT_NO_MEMORY();
         return -1;
     }
     return 0;
@@ -801,7 +808,7 @@ pymain_warnings_envvar(_PyMain *pymain)
            C89 wcstok */
         buf = (char *)PyMem_RawMalloc(strlen(p) + 1);
         if (buf == NULL) {
-            pymain->err = INIT_NO_MEMORY();
+            pymain->err = _Py_INIT_NO_MEMORY();
             return -1;
         }
         strcpy(buf, p);
@@ -811,13 +818,7 @@ pymain_warnings_envvar(_PyMain *pymain)
             size_t len;
             wchar_t *warning = Py_DecodeLocale(p, &len);
             if (warning == NULL) {
-                if (len == (size_t)-2) {
-                    pymain->err = _Py_INIT_ERR("failed to decode "
-                                               "PYTHONWARNINGS");
-                }
-                else {
-                    pymain->err = INIT_NO_MEMORY();
-                }
+                SET_DECODE_ERROR("PYTHONWARNINGS environment variable", len);
                 return -1;
             }
             if (pymain_optlist_append(pymain, &pymain->env_warning_options,
@@ -902,7 +903,7 @@ pymain_get_program_name(_PyMain *pymain)
 
         buffer = PyMem_RawMalloc(len * sizeof(wchar_t));
         if (buffer == NULL) {
-            pymain->err = INIT_NO_MEMORY();
+            pymain->err = _Py_INIT_NO_MEMORY();
             return -1;
         }
 
@@ -919,15 +920,8 @@ pymain_get_program_name(_PyMain *pymain)
             size_t len;
             wchar_t* wbuf = Py_DecodeLocale(pyvenv_launcher, &len);
             if (wbuf == NULL) {
-                if (len == (size_t)-2) {
-                    pymain->err = _Py_INIT_ERR("failed to decode "
-                                               "__PYVENV_LAUNCHER__");
-                    return -1;
-                }
-                else {
-                    pymain->err = INIT_NO_MEMORY();
-                    return -1;
-                }
+                SET_DECODE_ERROR("__PYVENV_LAUNCHER__", len);
+                return -1;
             }
             pymain->program_name = wbuf;
         }
@@ -1403,7 +1397,7 @@ pymain_get_env_var_dup(_PyMain *pymain, wchar_t **dest,
             return -2;
         }
         else {
-            pymain->err = INIT_NO_MEMORY();
+            pymain->err = _Py_INIT_NO_MEMORY();
             return -1;
         }
     }
@@ -1421,7 +1415,7 @@ pymain_init_pythonpath(_PyMain *pymain)
                                      L"PYTHONPATH", "PYTHONPATH");
     if (res < 0) {
         if (res == -2) {
-            pymain->err = _Py_INIT_ERR("failed to decode PYTHONPATH");
+            SET_DECODE_ERROR("PYTHONPATH", (size_t)-2);
         }
         return -1;
     }
@@ -1450,7 +1444,7 @@ pymain_init_pythonhome(_PyMain *pymain)
                                      L"PYTHONHOME", "PYTHONHOME");
     if (res < 0) {
         if (res == -2) {
-            pymain->err = _Py_INIT_ERR("failed to decode PYTHONHOME");
+            SET_DECODE_ERROR("PYTHONHOME", (size_t)-2);
         }
         return -1;
     }
diff --git a/PC/getpathp.c b/PC/getpathp.c
index 4756dc8abbb..e0cb9a25758 100644
--- a/PC/getpathp.c
+++ b/PC/getpathp.c
@@ -116,14 +116,34 @@
 #define LANDMARK L"lib\\os.py"
 #endif
 
-static wchar_t prefix[MAXPATHLEN+1];
-static wchar_t progpath[MAXPATHLEN+1];
-static wchar_t dllpath[MAXPATHLEN+1];
-static wchar_t *module_search_path = NULL;
+typedef struct {
+    wchar_t prefix[MAXPATHLEN+1];
+    wchar_t progpath[MAXPATHLEN+1];
+    wchar_t dllpath[MAXPATHLEN+1];
+    wchar_t *module_search_path;
+} PyPathConfig;
+
+typedef struct {
+    wchar_t *module_search_path_env;   /* PYTHONPATH environment variable */
+    wchar_t *path_env;                 /* PATH environment variable */
+    wchar_t *home;                     /* PYTHONHOME environment variable */
+
+    /* Registry key "Software\Python\PythonCore\PythonPath" */
+    wchar_t *machine_path;   /* from HKEY_LOCAL_MACHINE */
+    wchar_t *user_path;      /* from HKEY_CURRENT_USER */
+
+    wchar_t *prog;                     /* Program name */
+    wchar_t argv0_path[MAXPATHLEN+1];
+    wchar_t zip_path[MAXPATHLEN+1];
+} PyCalculatePath;
+
 
+static PyPathConfig path_config = {.module_search_path = NULL};
 
+
+/* determine if "ch" is a separator character */
 static int
-is_sep(wchar_t ch)      /* determine if "ch" is a separator character */
+is_sep(wchar_t ch)
 {
 #ifdef ALTSEP
     return ch == SEP || ch == ALTSEP;
@@ -132,28 +152,31 @@ is_sep(wchar_t ch)      /* determine if "ch" is a separator character */
 #endif
 }
 
+
 /* assumes 'dir' null terminated in bounds.  Never writes
-   beyond existing terminator.
-*/
+   beyond existing terminator. */
 static void
 reduce(wchar_t *dir)
 {
     size_t i = wcsnlen_s(dir, MAXPATHLEN+1);
-    if (i >= MAXPATHLEN+1)
+    if (i >= MAXPATHLEN+1) {
         Py_FatalError("buffer overflow in getpathp.c's reduce()");
+    }
 
     while (i > 0 && !is_sep(dir[i]))
         --i;
     dir[i] = '\0';
 }
 
+
 static int
 change_ext(wchar_t *dest, const wchar_t *src, const wchar_t *ext)
 {
     size_t src_len = wcsnlen_s(src, MAXPATHLEN+1);
     size_t i = src_len;
-    if (i >= MAXPATHLEN+1)
+    if (i >= MAXPATHLEN+1) {
         Py_FatalError("buffer overflow in getpathp.c's reduce()");
+    }
 
     while (i > 0 && src[i] != '.' && !is_sep(src[i]))
         --i;
@@ -163,11 +186,13 @@ change_ext(wchar_t *dest, const wchar_t *src, const wchar_t *ext)
         return -1;
     }
 
-    if (is_sep(src[i]))
+    if (is_sep(src[i])) {
         i = src_len;
+    }
 
     if (wcsncpy_s(dest, MAXPATHLEN+1, src, i) ||
-        wcscat_s(dest, MAXPATHLEN+1, ext)) {
+        wcscat_s(dest, MAXPATHLEN+1, ext))
+    {
         dest[0] = '\0';
         return -1;
     }
@@ -175,22 +200,25 @@ change_ext(wchar_t *dest, const wchar_t *src, const wchar_t *ext)
     return 0;
 }
 
+
 static int
 exists(wchar_t *filename)
 {
     return GetFileAttributesW(filename) != 0xFFFFFFFF;
 }
 
-/* Assumes 'filename' MAXPATHLEN+1 bytes long -
-   may extend 'filename' by one character.
-*/
+
+/* Is module -- check for .pyc too.
+   Assumes 'filename' MAXPATHLEN+1 bytes long -
+   may extend 'filename' by one character. */
 static int
-ismodule(wchar_t *filename, int update_filename) /* Is module -- check for .pyc too */
+ismodule(wchar_t *filename, int update_filename)
 {
     size_t n;
 
-    if (exists(filename))
+    if (exists(filename)) {
         return 1;
+    }
 
     /* Check for the compiled version of prefix. */
     n = wcsnlen_s(filename, MAXPATHLEN+1);
@@ -199,13 +227,15 @@ ismodule(wchar_t *filename, int update_filename) /* Is module -- check for .pyc
         filename[n] = L'c';
         filename[n + 1] = L'\0';
         exist = exists(filename);
-        if (!update_filename)
+        if (!update_filename) {
             filename[n] = L'\0';
+        }
         return exist;
     }
     return 0;
 }
 
+
 /* Add a path component, by appending stuff to buffer.
    buffer must have at least MAXPATHLEN + 1 bytes allocated, and contain a
    NUL-terminated string with no more than MAXPATHLEN characters (not counting
@@ -217,7 +247,9 @@ ismodule(wchar_t *filename, int update_filename) /* Is module -- check for .pyc
 */
 
 static int _PathCchCombineEx_Initialized = 0;
-typedef HRESULT(__stdcall *PPathCchCombineEx)(PWSTR pszPathOut, size_t cchPathOut, PCWSTR pszPathIn, PCWSTR pszMore, unsigned long dwFlags);
+typedef HRESULT(__stdcall *PPathCchCombineEx) (PWSTR pszPathOut, size_t cchPathOut,
+                                               PCWSTR pszPathIn, PCWSTR pszMore,
+                                               unsigned long dwFlags);
 static PPathCchCombineEx _PathCchCombineEx;
 
 static void
@@ -225,28 +257,32 @@ join(wchar_t *buffer, const wchar_t *stuff)
 {
     if (_PathCchCombineEx_Initialized == 0) {
         HMODULE pathapi = LoadLibraryW(L"api-ms-win-core-path-l1-1-0.dll");
-        if (pathapi)
+        if (pathapi) {
             _PathCchCombineEx = (PPathCchCombineEx)GetProcAddress(pathapi, "PathCchCombineEx");
-        else
+        }
+        else {
             _PathCchCombineEx = NULL;
+        }
         _PathCchCombineEx_Initialized = 1;
     }
 
     if (_PathCchCombineEx) {
-        if (FAILED(_PathCchCombineEx(buffer, MAXPATHLEN+1, buffer, stuff, 0)))
+        if (FAILED(_PathCchCombineEx(buffer, MAXPATHLEN+1, buffer, stuff, 0))) {
             Py_FatalError("buffer overflow in getpathp.c's join()");
+        }
     } else {
-        if (!PathCombineW(buffer, buffer, stuff))
+        if (!PathCombineW(buffer, buffer, stuff)) {
             Py_FatalError("buffer overflow in getpathp.c's join()");
+        }
     }
 }
 
+
 /* gotlandmark only called by search_for_prefix, which ensures
    'prefix' is null terminated in bounds.  join() ensures
-   'landmark' can not overflow prefix if too long.
-*/
+   'landmark' can not overflow prefix if too long. */
 static int
-gotlandmark(const wchar_t *landmark)
+gotlandmark(wchar_t *prefix, const wchar_t *landmark)
 {
     int ok;
     Py_ssize_t n = wcsnlen_s(prefix, MAXPATHLEN);
@@ -257,27 +293,29 @@ gotlandmark(const wchar_t *landmark)
     return ok;
 }
 
+
 /* assumes argv0_path is MAXPATHLEN+1 bytes long, already \0 term'd.
    assumption provided by only caller, calculate_path() */
 static int
-search_for_prefix(wchar_t *argv0_path, const wchar_t *landmark)
+search_for_prefix(wchar_t *prefix, wchar_t *argv0_path, const wchar_t *landmark)
 {
     /* Search from argv0_path, until landmark is found */
     wcscpy_s(prefix, MAXPATHLEN + 1, argv0_path);
     do {
-        if (gotlandmark(landmark))
+        if (gotlandmark(prefix, landmark)) {
             return 1;
+        }
         reduce(prefix);
     } while (prefix[0]);
     return 0;
 }
 
+
 #ifdef Py_ENABLE_SHARED
 
 /* a string loaded from the DLL at startup.*/
 extern const char *PyWin_DLLVersionString;
 
-
 /* Load a PYTHONPATH value from the registry.
    Load from either HKEY_LOCAL_MACHINE or HKEY_CURRENT_USER.
 
@@ -290,7 +328,6 @@ extern const char *PyWin_DLLVersionString;
    work on Win16, where the buffer sizes werent available
    in advance.  It could be simplied now Win16/Win32s is dead!
 */
-
 static wchar_t *
 getpythonregpath(HKEY keyBase, int skipcore)
 {
@@ -315,7 +352,9 @@ getpythonregpath(HKEY keyBase, int skipcore)
                 sizeof(WCHAR)*(versionLen-1) +
                 sizeof(keySuffix);
     keyBuf = keyBufPtr = PyMem_RawMalloc(keyBufLen);
-    if (keyBuf==NULL) goto done;
+    if (keyBuf==NULL) {
+        goto done;
+    }
 
     memcpy_s(keyBufPtr, keyBufLen, keyPrefix, sizeof(keyPrefix)-sizeof(WCHAR));
     keyBufPtr += Py_ARRAY_LENGTH(keyPrefix) - 1;
@@ -329,17 +368,25 @@ getpythonregpath(HKEY keyBase, int skipcore)
             0, /* reserved */
             KEY_READ,
             &newKey);
-    if (rc!=ERROR_SUCCESS) goto done;
+    if (rc!=ERROR_SUCCESS) {
+        goto done;
+    }
     /* Find out how big our core buffer is, and how many subkeys we have */
     rc = RegQueryInfoKey(newKey, NULL, NULL, NULL, &numKeys, NULL, NULL,
                     NULL, NULL, &dataSize, NULL, NULL);
-    if (rc!=ERROR_SUCCESS) goto done;
-    if (skipcore) dataSize = 0; /* Only count core ones if we want them! */
+    if (rc!=ERROR_SUCCESS) {
+        goto done;
+    }
+    if (skipcore) {
+        dataSize = 0; /* Only count core ones if we want them! */
+    }
     /* Allocate a temp array of char buffers, so we only need to loop
        reading the registry once
     */
     ppPaths = PyMem_RawMalloc( sizeof(WCHAR *) * numKeys );
-    if (ppPaths==NULL) goto done;
+    if (ppPaths==NULL) {
+        goto done;
+    }
     memset(ppPaths, 0, sizeof(WCHAR *) * numKeys);
     /* Loop over all subkeys, allocating a temp sub-buffer. */
     for(index=0;index<numKeys;index++) {
@@ -349,14 +396,18 @@ getpythonregpath(HKEY keyBase, int skipcore)
         /* Get the sub-key name */
         DWORD rc = RegEnumKeyExW(newKey, index, keyBuf, &reqdSize,
                                  NULL, NULL, NULL, NULL );
-        if (rc!=ERROR_SUCCESS) goto done;
+        if (rc!=ERROR_SUCCESS) {
+            goto done;
+        }
         /* Open the sub-key */
         rc=RegOpenKeyExW(newKey,
                                         keyBuf, /* subkey */
                         0, /* reserved */
                         KEY_READ,
                         &subKey);
-        if (rc!=ERROR_SUCCESS) goto done;
+        if (rc!=ERROR_SUCCESS) {
+            goto done;
+        }
         /* Find the value of the buffer size, malloc, then read it */
         RegQueryValueExW(subKey, NULL, 0, NULL, NULL, &reqdSize);
         if (reqdSize) {
@@ -372,7 +423,9 @@ getpythonregpath(HKEY keyBase, int skipcore)
     }
 
     /* return null if no path to return */
-    if (dataSize == 0) goto done;
+    if (dataSize == 0) {
+        goto done;
+    }
 
     /* original datasize from RegQueryInfo doesn't include the \0 */
     dataBuf = PyMem_RawMalloc((dataSize+1) * sizeof(WCHAR));
@@ -392,8 +445,9 @@ getpythonregpath(HKEY keyBase, int skipcore)
                 dataSize -= (DWORD)len;
             }
         }
-        if (skipcore)
+        if (skipcore) {
             *szCur = '\0';
+        }
         else {
             /* If we have no values, we don't need a ';' */
             if (numKeys) {
@@ -420,33 +474,34 @@ getpythonregpath(HKEY keyBase, int skipcore)
             PyMem_RawFree(ppPaths[index]);
         PyMem_RawFree(ppPaths);
     }
-    if (newKey)
+    if (newKey) {
         RegCloseKey(newKey);
+    }
     PyMem_RawFree(keyBuf);
     return retval;
 }
 #endif /* Py_ENABLE_SHARED */
 
+
 static void
-get_progpath(void)
+get_progpath(PyCalculatePath *calculate, wchar_t *progpath, wchar_t *dllpath)
 {
-    extern wchar_t *Py_GetProgramName(void);
-    wchar_t *path = _wgetenv(L"PATH");
-    wchar_t *prog = Py_GetProgramName();
+    wchar_t *path = calculate->path_env;
 
 #ifdef Py_ENABLE_SHARED
     extern HANDLE PyWin_DLLhModule;
     /* static init of progpath ensures final char remains \0 */
-    if (PyWin_DLLhModule)
-        if (!GetModuleFileNameW(PyWin_DLLhModule, dllpath, MAXPATHLEN))
+    if (PyWin_DLLhModule) {
+        if (!GetModuleFileNameW(PyWin_DLLhModule, dllpath, MAXPATHLEN)) {
             dllpath[0] = 0;
+        }
+    }
 #else
     dllpath[0] = 0;
 #endif
-    if (GetModuleFileNameW(NULL, progpath, MAXPATHLEN))
+    if (GetModuleFileNameW(NULL, progpath, MAXPATHLEN)) {
         return;
-    if (prog == NULL || *prog == '\0')
-        prog = L"python";
+    }
 
     /* If there is no slash in the argv0 path, then we have to
      * assume python is on the user's $PATH, since there's no
@@ -454,11 +509,13 @@ get_progpath(void)
      * $PATH isn't exported, you lose.
      */
 #ifdef ALTSEP
-    if (wcschr(prog, SEP) || wcschr(prog, ALTSEP))
+    if (wcschr(calculate->prog, SEP) || wcschr(calculate->prog, ALTSEP))
 #else
-    if (wcschr(prog, SEP))
+    if (wcschr(calculate->prog, SEP))
 #endif
-        wcsncpy(progpath, prog, MAXPATHLEN);
+    {
+        wcsncpy(progpath, calculate->prog, MAXPATHLEN);
+    }
     else if (path) {
         while (1) {
             wchar_t *delim = wcschr(path, DELIM);
@@ -470,13 +527,15 @@ get_progpath(void)
                 wcsncpy(progpath, path, len);
                 *(progpath + len) = '\0';
             }
-            else
+            else {
                 wcsncpy(progpath, path, MAXPATHLEN);
+            }
 
             /* join() is safe for MAXPATHLEN+1 size buffer */
-            join(progpath, prog);
-            if (exists(progpath))
+            join(progpath, calculate->prog);
+            if (exists(progpath)) {
                 break;
+            }
 
             if (!delim) {
                 progpath[0] = '\0';
@@ -485,10 +544,12 @@ get_progpath(void)
             path = delim + 1;
         }
     }
-    else
+    else {
         progpath[0] = '\0';
+    }
 }
 
+
 static int
 find_env_config_value(FILE * env_file, const wchar_t * key, wchar_t * value)
 {
@@ -502,15 +563,18 @@ find_env_config_value(FILE * env_file, const wchar_t * key, wchar_t * value)
         PyObject * decoded;
         size_t n;
 
-        if (p == NULL)
+        if (p == NULL) {
             break;
+        }
         n = strlen(p);
         if (p[n - 1] != '\n') {
             /* line has overflowed - bail */
             break;
         }
-        if (p[0] == '#')    /* Comment - skip */
+        if (p[0] == '#') {
+            /* Comment - skip */
             continue;
+        }
         decoded = PyUnicode_DecodeUTF8(buffer, n, "surrogateescape");
         if (decoded != NULL) {
             Py_ssize_t k;
@@ -537,12 +601,14 @@ find_env_config_value(FILE * env_file, const wchar_t * key, wchar_t * value)
     return result;
 }
 
-static int
+
+static wchar_t*
 read_pth_file(const wchar_t *path, wchar_t *prefix, int *isolated, int *nosite)
 {
     FILE *sp_file = _Py_wfopen(path, L"r");
-    if (sp_file == NULL)
-        return -1;
+    if (sp_file == NULL) {
+        return NULL;
+    }
 
     wcscpy_s(prefix, MAXPATHLEN+1, path);
     reduce(prefix);
@@ -558,10 +624,12 @@ read_pth_file(const wchar_t *path, wchar_t *prefix, int *isolated, int *nosite)
     while (!feof(sp_file)) {
         char line[MAXPATHLEN + 1];
         char *p = fgets(line, MAXPATHLEN + 1, sp_file);
-        if (!p)
+        if (!p) {
             break;
-        if (*p == '\0' || *p == '\r' || *p == '\n' || *p == '#')
+        }
+        if (*p == '\0' || *p == '\r' || *p == '\n' || *p == '#') {
             continue;
+        }
         while (*++p) {
             if (*p == '\r' || *p == '\n') {
                 *p = '\0';
@@ -611,126 +679,176 @@ read_pth_file(const wchar_t *path, wchar_t *prefix, int *isolated, int *nosite)
         PyMem_RawFree(wline);
     }
 
-    module_search_path = buf;
-
     fclose(sp_file);
-    return 0;
+    return buf;
 
 error:
     PyMem_RawFree(buf);
     fclose(sp_file);
-    return -1;
+    return NULL;
 }
 
 
-static void
-calculate_path(const _PyMainInterpreterConfig *config)
+static _PyInitError
+calculate_init(PyCalculatePath *calculate,
+               const _PyMainInterpreterConfig *main_config)
 {
-    wchar_t argv0_path[MAXPATHLEN+1];
-    wchar_t *buf;
-    size_t bufsz;
-    wchar_t *pythonhome = _Py_GetPythonHomeWithConfig(config);
-    wchar_t *envpath = NULL;
-
-    int skiphome, skipdefault;
-    wchar_t *machinepath = NULL;
-    wchar_t *userpath = NULL;
-    wchar_t zip_path[MAXPATHLEN+1];
+    _PyInitError err;
 
-    if (config) {
-        envpath = config->module_search_path_env;
+    err = _Py_GetPythonHomeWithConfig(main_config, &calculate->home);
+    if (_Py_INIT_FAILED(err)) {
+        return err;
+    }
+
+    if (main_config) {
+        calculate->module_search_path_env = main_config->module_search_path_env;
     }
     else if (!Py_IgnoreEnvironmentFlag) {
-        envpath = _wgetenv(L"PYTHONPATH");
-        if (envpath && *envpath == '\0')
-            envpath = NULL;
+        wchar_t *path = _wgetenv(L"PYTHONPATH");
+        if (path && *path != '\0') {
+            calculate->module_search_path_env = path;
+        }
     }
 
-    get_progpath();
-    /* progpath guaranteed \0 terminated in MAXPATH+1 bytes. */
-    wcscpy_s(argv0_path, MAXPATHLEN+1, progpath);
-    reduce(argv0_path);
+    calculate->path_env = _wgetenv(L"PATH");
 
-    /* Search for a sys.path file */
-    {
-        wchar_t spbuffer[MAXPATHLEN+1];
+    wchar_t *prog = Py_GetProgramName();
+    if (prog == NULL || *prog == '\0') {
+        prog = L"python";
+    }
+    calculate->prog = prog;
 
-        if ((dllpath[0] && !change_ext(spbuffer, dllpath, L"._pth") && exists(spbuffer)) ||
-            (progpath[0] && !change_ext(spbuffer, progpath, L"._pth") && exists(spbuffer))) {
+    return _Py_INIT_OK();
+}
 
-            if (!read_pth_file(spbuffer, prefix, &Py_IsolatedFlag, &Py_NoSiteFlag)) {
-                return;
-            }
+
+static int
+get_pth_filename(wchar_t *spbuffer, PyPathConfig *config)
+{
+    if (config->dllpath[0]) {
+        if (!change_ext(spbuffer, config->dllpath, L"._pth") && exists(spbuffer)) {
+            return 1;
+        }
+    }
+    if (config->progpath[0]) {
+        if (!change_ext(spbuffer, config->progpath, L"._pth") && exists(spbuffer)) {
+            return 1;
         }
     }
+    return 0;
+}
 
-    /* Search for an environment configuration file, first in the
-       executable's directory and then in the parent directory.
-       If found, open it for use when searching for prefixes.
-    */
 
-    {
-        wchar_t envbuffer[MAXPATHLEN+1];
-        wchar_t tmpbuffer[MAXPATHLEN+1];
-        const wchar_t *env_cfg = L"pyvenv.cfg";
-        FILE * env_file = NULL;
+static int
+calculate_pth_file(PyPathConfig *config)
+{
+    wchar_t spbuffer[MAXPATHLEN+1];
+
+    if (!get_pth_filename(spbuffer, config)) {
+        return 0;
+    }
+
+    config->module_search_path = read_pth_file(spbuffer, config->prefix,
+                                               &Py_IsolatedFlag,
+                                               &Py_NoSiteFlag);
+    if (!config->module_search_path) {
+        return 0;
+    }
+    return 1;
+}
+
+
+/* Search for an environment configuration file, first in the
+   executable's directory and then in the parent directory.
+   If found, open it for use when searching for prefixes.
+*/
+static void
+calculate_pyvenv_file(PyCalculatePath *calculate)
+{
+    wchar_t envbuffer[MAXPATHLEN+1];
+    const wchar_t *env_cfg = L"pyvenv.cfg";
+
+    wcscpy_s(envbuffer, MAXPATHLEN+1, calculate->argv0_path);
+    join(envbuffer, env_cfg);
 
-        wcscpy_s(envbuffer, MAXPATHLEN+1, argv0_path);
+    FILE *env_file = _Py_wfopen(envbuffer, L"r");
+    if (env_file == NULL) {
+        errno = 0;
+        reduce(envbuffer);
+        reduce(envbuffer);
         join(envbuffer, env_cfg);
         env_file = _Py_wfopen(envbuffer, L"r");
         if (env_file == NULL) {
             errno = 0;
-            reduce(envbuffer);
-            reduce(envbuffer);
-            join(envbuffer, env_cfg);
-            env_file = _Py_wfopen(envbuffer, L"r");
-            if (env_file == NULL) {
-                errno = 0;
-            }
-        }
-        if (env_file != NULL) {
-            /* Look for a 'home' variable and set argv0_path to it, if found */
-            if (find_env_config_value(env_file, L"home", tmpbuffer)) {
-                wcscpy_s(argv0_path, MAXPATHLEN+1, tmpbuffer);
-            }
-            fclose(env_file);
-            env_file = NULL;
         }
     }
 
-    /* Calculate zip archive path from DLL or exe path */
-    change_ext(zip_path, dllpath[0] ? dllpath : progpath, L".zip");
+    if (env_file == NULL) {
+        return;
+    }
 
-    if (pythonhome == NULL || *pythonhome == '\0') {
-        if (zip_path[0] && exists(zip_path)) {
-            wcscpy_s(prefix, MAXPATHLEN+1, zip_path);
-            reduce(prefix);
-            pythonhome = prefix;
-        } else if (search_for_prefix(argv0_path, LANDMARK))
-            pythonhome = prefix;
-        else
-            pythonhome = NULL;
+    /* Look for a 'home' variable and set argv0_path to it, if found */
+    wchar_t tmpbuffer[MAXPATHLEN+1];
+    if (find_env_config_value(env_file, L"home", tmpbuffer)) {
+        wcscpy_s(calculate->argv0_path, MAXPATHLEN+1, tmpbuffer);
     }
-    else
-        wcscpy_s(prefix, MAXPATHLEN+1, pythonhome);
+    fclose(env_file);
+}
 
 
-    skiphome = pythonhome==NULL ? 0 : 1;
+static void
+calculate_path_impl(PyCalculatePath *calculate, PyPathConfig *config,
+                    const _PyMainInterpreterConfig *main_config)
+{
+    get_progpath(calculate, config->progpath, config->dllpath);
+    /* progpath guaranteed \0 terminated in MAXPATH+1 bytes. */
+    wcscpy_s(calculate->argv0_path, MAXPATHLEN+1, config->progpath);
+    reduce(calculate->argv0_path);
+
+    /* Search for a sys.path file */
+    if (calculate_pth_file(config)) {
+        return;
+    }
+
+    calculate_pyvenv_file(calculate);
+
+    /* Calculate zip archive path from DLL or exe path */
+    change_ext(calculate->zip_path,
+               config->dllpath[0] ? config->dllpath : config->progpath,
+               L".zip");
+
+    if (calculate->home == NULL || *calculate->home == '\0') {
+        if (calculate->zip_path[0] && exists(calculate->zip_path)) {
+            wcscpy_s(config->prefix, MAXPATHLEN+1, calculate->zip_path);
+            reduce(config->prefix);
+            calculate->home = config->prefix;
+        } else if (search_for_prefix(config->prefix, calculate->argv0_path, LANDMARK)) {
+            calculate->home = config->prefix;
+        }
+        else {
+            calculate->home = NULL;
+        }
+    }
+    else {
+        wcscpy_s(config->prefix, MAXPATHLEN+1, calculate->home);
+    }
+
+    int skiphome = calculate->home==NULL ? 0 : 1;
 #ifdef Py_ENABLE_SHARED
-    machinepath = getpythonregpath(HKEY_LOCAL_MACHINE, skiphome);
-    userpath = getpythonregpath(HKEY_CURRENT_USER, skiphome);
+    calculate->machine_path = getpythonregpath(HKEY_LOCAL_MACHINE, skiphome);
+    calculate->user_path = getpythonregpath(HKEY_CURRENT_USER, skiphome);
 #endif
     /* We only use the default relative PYTHONPATH if we haven't
        anything better to use! */
-    skipdefault = envpath!=NULL || pythonhome!=NULL || \
-                  machinepath!=NULL || userpath!=NULL;
+    int skipdefault = (calculate->module_search_path_env!=NULL || calculate->home!=NULL || \
+                       calculate->machine_path!=NULL || calculate->user_path!=NULL);
 
     /* We need to construct a path from the following parts.
        (1) the PYTHONPATH environment variable, if set;
        (2) for Win32, the zip archive file path;
-       (3) for Win32, the machinepath and userpath, if set;
+       (3) for Win32, the machine_path and user_path, if set;
        (4) the PYTHONPATH config macro, with the leading "."
-           of each component replaced with pythonhome, if set;
+           of each component replaced with home, if set;
        (5) the directory containing the executable (argv0_path).
        The length calculation calculates #4 first.
        Extra rules:
@@ -739,74 +857,80 @@ calculate_path(const _PyMainInterpreterConfig *config)
     */
 
     /* Calculate size of return buffer */
-    if (pythonhome != NULL) {
+    size_t bufsz = 0;
+    if (calculate->home != NULL) {
         wchar_t *p;
         bufsz = 1;
         for (p = PYTHONPATH; *p; p++) {
-            if (*p == DELIM)
+            if (*p == DELIM) {
                 bufsz++; /* number of DELIM plus one */
+            }
         }
-        bufsz *= wcslen(pythonhome);
+        bufsz *= wcslen(calculate->home);
     }
-    else
-        bufsz = 0;
     bufsz += wcslen(PYTHONPATH) + 1;
-    bufsz += wcslen(argv0_path) + 1;
-    if (userpath)
-        bufsz += wcslen(userpath) + 1;
-    if (machinepath)
-        bufsz += wcslen(machinepath) + 1;
-    bufsz += wcslen(zip_path) + 1;
-    if (envpath != NULL)
-        bufsz += wcslen(envpath) + 1;
-
-    module_search_path = buf = PyMem_RawMalloc(bufsz*sizeof(wchar_t));
+    bufsz += wcslen(calculate->argv0_path) + 1;
+    if (calculate->user_path) {
+        bufsz += wcslen(calculate->user_path) + 1;
+    }
+    if (calculate->machine_path) {
+        bufsz += wcslen(calculate->machine_path) + 1;
+    }
+    bufsz += wcslen(calculate->zip_path) + 1;
+    if (calculate->module_search_path_env != NULL) {
+        bufsz += wcslen(calculate->module_search_path_env) + 1;
+    }
+
+    wchar_t *buf, *start_buf;
+    buf = PyMem_RawMalloc(bufsz * sizeof(wchar_t));
     if (buf == NULL) {
         /* We can't exit, so print a warning and limp along */
         fprintf(stderr, "Can't malloc dynamic PYTHONPATH.\n");
-        if (envpath) {
+        if (calculate->module_search_path_env) {
             fprintf(stderr, "Using environment $PYTHONPATH.\n");
-            module_search_path = envpath;
+            config->module_search_path = calculate->module_search_path_env;
         }
         else {
             fprintf(stderr, "Using default static path.\n");
-            module_search_path = PYTHONPATH;
+            config->module_search_path = PYTHONPATH;
         }
-        PyMem_RawFree(machinepath);
-        PyMem_RawFree(userpath);
         return;
     }
+    start_buf = buf;
 
-    if (envpath) {
-        if (wcscpy_s(buf, bufsz - (buf - module_search_path), envpath))
+    if (calculate->module_search_path_env) {
+        if (wcscpy_s(buf, bufsz - (buf - start_buf), calculate->module_search_path_env)) {
             Py_FatalError("buffer overflow in getpathp.c's calculate_path()");
+        }
         buf = wcschr(buf, L'\0');
         *buf++ = DELIM;
     }
-    if (zip_path[0]) {
-        if (wcscpy_s(buf, bufsz - (buf - module_search_path), zip_path))
+    if (calculate->zip_path[0]) {
+        if (wcscpy_s(buf, bufsz - (buf - start_buf), calculate->zip_path)) {
             Py_FatalError("buffer overflow in getpathp.c's calculate_path()");
+        }
         buf = wcschr(buf, L'\0');
         *buf++ = DELIM;
     }
-    if (userpath) {
-        if (wcscpy_s(buf, bufsz - (buf - module_search_path), userpath))
+    if (calculate->user_path) {
+        if (wcscpy_s(buf, bufsz - (buf - start_buf), calculate->user_path)) {
             Py_FatalError("buffer overflow in getpathp.c's calculate_path()");
+        }
         buf = wcschr(buf, L'\0');
         *buf++ = DELIM;
-        PyMem_RawFree(userpath);
     }
-    if (machinepath) {
-        if (wcscpy_s(buf, bufsz - (buf - module_search_path), machinepath))
+    if (calculate->machine_path) {
+        if (wcscpy_s(buf, bufsz - (buf - start_buf), calculate->machine_path)) {
             Py_FatalError("buffer overflow in getpathp.c's calculate_path()");
+        }
         buf = wcschr(buf, L'\0');
         *buf++ = DELIM;
-        PyMem_RawFree(machinepath);
     }
-    if (pythonhome == NULL) {
+    if (calculate->home == NULL) {
         if (!skipdefault) {
-            if (wcscpy_s(buf, bufsz - (buf - module_search_path), PYTHONPATH))
+            if (wcscpy_s(buf, bufsz - (buf - start_buf), PYTHONPATH)) {
                 Py_FatalError("buffer overflow in getpathp.c's calculate_path()");
+            }
             buf = wcschr(buf, L'\0');
             *buf++ = DELIM;
         }
@@ -816,13 +940,16 @@ calculate_path(const _PyMainInterpreterConfig *config)
         size_t n;
         for (;;) {
             q = wcschr(p, DELIM);
-            if (q == NULL)
+            if (q == NULL) {
                 n = wcslen(p);
-            else
+            }
+            else {
                 n = q-p;
+            }
             if (p[0] == '.' && is_sep(p[1])) {
-                if (wcscpy_s(buf, bufsz - (buf - module_search_path), pythonhome))
+                if (wcscpy_s(buf, bufsz - (buf - start_buf), calculate->home)) {
                     Py_FatalError("buffer overflow in getpathp.c's calculate_path()");
+                }
                 buf = wcschr(buf, L'\0');
                 p++;
                 n--;
@@ -830,17 +957,19 @@ calculate_path(const _PyMainInterpreterConfig *config)
             wcsncpy(buf, p, n);
             buf += n;
             *buf++ = DELIM;
-            if (q == NULL)
+            if (q == NULL) {
                 break;
+            }
             p = q+1;
         }
     }
-    if (argv0_path) {
-        wcscpy(buf, argv0_path);
+    if (calculate->argv0_path) {
+        wcscpy(buf, calculate->argv0_path);
         buf = wcschr(buf, L'\0');
         *buf++ = DELIM;
     }
     *(buf - 1) = L'\0';
+
     /* Now to pull one last hack/trick.  If sys.prefix is
        empty, then try and find it somewhere on the paths
        we calculated.  We scan backwards, as our general policy
@@ -849,7 +978,7 @@ calculate_path(const _PyMainInterpreterConfig *config)
        on the path, and that our 'prefix' directory is
        the parent of that.
     */
-    if (*prefix==L'\0') {
+    if (config->prefix[0] == L'\0') {
         wchar_t lookBuf[MAXPATHLEN+1];
         wchar_t *look = buf - 1; /* 'buf' is at the end of the buffer */
         while (1) {
@@ -859,84 +988,129 @@ calculate_path(const _PyMainInterpreterConfig *config)
                start of the path in question - even if this
                is one character before the start of the buffer
             */
-            while (look >= module_search_path && *look != DELIM)
+            while (look >= start_buf && *look != DELIM)
                 look--;
             nchars = lookEnd-look;
             wcsncpy(lookBuf, look+1, nchars);
             lookBuf[nchars] = L'\0';
             /* Up one level to the parent */
             reduce(lookBuf);
-            if (search_for_prefix(lookBuf, LANDMARK)) {
+            if (search_for_prefix(config->prefix, lookBuf, LANDMARK)) {
                 break;
             }
             /* If we are out of paths to search - give up */
-            if (look < module_search_path)
+            if (look < start_buf) {
                 break;
+            }
             look--;
         }
     }
+
+    config->module_search_path = start_buf;
+}
+
+
+static void
+calculate_free(PyCalculatePath *calculate)
+{
+    PyMem_RawFree(calculate->machine_path);
+    PyMem_RawFree(calculate->user_path);
 }
 
+static void
+calculate_path(const _PyMainInterpreterConfig *main_config)
+{
+    PyCalculatePath calculate;
+    memset(&calculate, 0, sizeof(calculate));
+
+    _PyInitError err = calculate_init(&calculate, main_config);
+    if (_Py_INIT_FAILED(err)) {
+        calculate_free(&calculate);
+        _Py_FatalInitError(err);
+    }
+
+    PyPathConfig new_path_config;
+    memset(&new_path_config, 0, sizeof(new_path_config));
+
+    calculate_path_impl(&calculate, &new_path_config, main_config);
+    path_config = new_path_config;
+
+    calculate_free(&calculate);
+}
+
+
 
 /* External interface */
 
 void
 Py_SetPath(const wchar_t *path)
 {
-    if (module_search_path != NULL) {
-        PyMem_RawFree(module_search_path);
-        module_search_path = NULL;
-    }
-    if (path != NULL) {
-        extern wchar_t *Py_GetProgramName(void);
-        wchar_t *prog = Py_GetProgramName();
-        wcsncpy(progpath, prog, MAXPATHLEN);
-        prefix[0] = L'\0';
-        module_search_path = PyMem_RawMalloc((wcslen(path) + 1) * sizeof(wchar_t));
-        if (module_search_path != NULL)
-            wcscpy(module_search_path, path);
+    if (path_config.module_search_path != NULL) {
+        PyMem_RawFree(path_config.module_search_path);
+        path_config.module_search_path = NULL;
+    }
+
+    if (path == NULL) {
+        return;
+    }
+
+    wchar_t *prog = Py_GetProgramName();
+    wcsncpy(path_config.progpath, prog, MAXPATHLEN);
+    path_config.prefix[0] = L'\0';
+    path_config.module_search_path = PyMem_RawMalloc((wcslen(path) + 1) * sizeof(wchar_t));
+    if (path_config.module_search_path != NULL) {
+        wcscpy(path_config.module_search_path, path);
     }
 }
 
+
 wchar_t *
-_Py_GetPathWithConfig(const _PyMainInterpreterConfig *config)
+_Py_GetPathWithConfig(const _PyMainInterpreterConfig *main_config)
 {
-    if (!module_search_path) {
-        calculate_path(config);
+    if (!path_config.module_search_path) {
+        calculate_path(main_config);
     }
-    return module_search_path;
+    return path_config.module_search_path;
 }
 
+
 wchar_t *
 Py_GetPath(void)
 {
-    if (!module_search_path)
+    if (!path_config.module_search_path) {
         calculate_path(NULL);
-    return module_search_path;
+    }
+    return path_config.module_search_path;
 }
 
+
 wchar_t *
 Py_GetPrefix(void)
 {
-    if (!module_search_path)
+    if (!path_config.module_search_path) {
         calculate_path(NULL);
-    return prefix;
+    }
+    return path_config.prefix;
 }
 
+
 wchar_t *
 Py_GetExecPrefix(void)
 {
     return Py_GetPrefix();
 }
 
+
 wchar_t *
 Py_GetProgramFullPath(void)
 {
-    if (!module_search_path)
+    if (!path_config.module_search_path) {
         calculate_path(NULL);
-    return progpath;
+    }
+    return path_config.progpath;
 }
 
+
 /* Load python3.dll before loading any extension module that might refer
    to it. That way, we can be sure that always the python3.dll corresponding
    to this python DLL is loaded, not a python3.dll that might be on the path
@@ -950,20 +1124,23 @@ _Py_CheckPython3()
 {
     wchar_t py3path[MAXPATHLEN+1];
     wchar_t *s;
-    if (python3_checked)
+    if (python3_checked) {
         return hPython3 != NULL;
+    }
     python3_checked = 1;
 
     /* If there is a python3.dll next to the python3y.dll,
        assume this is a build tree; use that DLL */
-    wcscpy(py3path, dllpath);
+    wcscpy(py3path, path_config.dllpath);
     s = wcsrchr(py3path, L'\\');
-    if (!s)
+    if (!s) {
         s = py3path;
+    }
     wcscpy(s, L"\\python3.dll");
     hPython3 = LoadLibraryExW(py3path, NULL, LOAD_WITH_ALTERED_SEARCH_PATH);
-    if (hPython3 != NULL)
+    if (hPython3 != NULL) {
         return 1;
+    }
 
     /* Check sys.prefix\DLLs\python3.dll */
     wcscpy(py3path, Py_GetPrefix());
diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c
index 5bbbbc68f08..8d2ec4e91c3 100644
--- a/Python/pylifecycle.c
+++ b/Python/pylifecycle.c
@@ -1477,8 +1477,9 @@ Py_SetPythonHome(wchar_t *home)
     default_home = home;
 }
 
-wchar_t *
-_Py_GetPythonHomeWithConfig(const _PyMainInterpreterConfig *config)
+
+_PyInitError
+_Py_GetPythonHomeWithConfig(const _PyMainInterpreterConfig *config, wchar_t **homep)
 {
     /* Use a static buffer to avoid heap memory allocation failure.
        Py_GetPythonHome() doesn't allow to report error, and the caller
@@ -1486,32 +1487,40 @@ _Py_GetPythonHomeWithConfig(const _PyMainInterpreterConfig *config)
     static wchar_t buffer[MAXPATHLEN+1];
 
     if (default_home) {
-        return default_home;
+        *homep = default_home;
+        return _Py_INIT_OK();
     }
 
     if (config) {
-        return config->pythonhome;
+        *homep = config->pythonhome;
+        return _Py_INIT_OK();
     }
 
     char *home = Py_GETENV("PYTHONHOME");
     if (!home) {
-        return NULL;
+        *homep = NULL;
+        return _Py_INIT_OK();
     }
 
     size_t size = Py_ARRAY_LENGTH(buffer);
     size_t r = mbstowcs(buffer, home, size);
     if (r == (size_t)-1 || r >= size) {
         /* conversion failed or the static buffer is too small */
-        return NULL;
+        *homep = NULL;
+        return _Py_INIT_ERR("failed to decode PYTHONHOME environment variable");
     }
 
-    return buffer;
+    *homep = buffer;
+    return _Py_INIT_OK();
 }
 
 wchar_t *
 Py_GetPythonHome(void)
 {
-    return _Py_GetPythonHomeWithConfig(NULL);
+    wchar_t *home;
+    /* Ignore error */
+    (void)_Py_GetPythonHomeWithConfig(NULL, &home);
+    return home;
 }
 
 /* Add the __main__ module */



More information about the Python-checkins mailing list