[Python-checkins] bpo-38353: getpath.c: allocates strings on the heap (GH-16585)

Victor Stinner webhook-mailer at python.org
Fri Oct 4 13:54:02 EDT 2019


https://github.com/python/cpython/commit/c02b41b1fb115c87693530ea6a480b2e15460424
commit: c02b41b1fb115c87693530ea6a480b2e15460424
branch: master
author: Victor Stinner <vstinner at python.org>
committer: GitHub <noreply at github.com>
date: 2019-10-04T19:53:43+02:00
summary:

bpo-38353: getpath.c: allocates strings on the heap (GH-16585)

* _Py_FindEnvConfigValue() now returns a string allocated
  by PyMem_RawMalloc().
* calculate_init() now decodes VPATH macro.
* Add calculate_open_pyenv() function.
* Add substring() and joinpath2() functions.

* Fix add_exe_suffix()

And a few cleanup changes.

files:
M Include/internal/pycore_pathconfig.h
M Modules/getpath.c
M PC/getpathp.c
M Python/pathconfig.c

diff --git a/Include/internal/pycore_pathconfig.h b/Include/internal/pycore_pathconfig.h
index ce75ccee835a2..257c056a77d0d 100644
--- a/Include/internal/pycore_pathconfig.h
+++ b/Include/internal/pycore_pathconfig.h
@@ -56,11 +56,10 @@ extern PyStatus _PyPathConfig_Calculate(
 extern int _PyPathConfig_ComputeSysPath0(
     const PyWideStringList *argv,
     PyObject **path0);
-extern int _Py_FindEnvConfigValue(
+extern PyStatus _Py_FindEnvConfigValue(
     FILE *env_file,
     const wchar_t *key,
-    wchar_t *value,
-    size_t value_size);
+    wchar_t **value_p);
 
 #ifdef MS_WINDOWS
 extern wchar_t* _Py_GetDLLPath(void);
diff --git a/Modules/getpath.c b/Modules/getpath.c
index 5f35ca6e319d8..993276fdc27d0 100644
--- a/Modules/getpath.c
+++ b/Modules/getpath.c
@@ -113,6 +113,8 @@ extern "C" {
 #define LANDMARK L"os.py"
 #endif
 
+#define BUILD_LANDMARK L"Modules/Setup.local"
+
 #define DECODE_LOCALE_ERR(NAME, LEN) \
     ((LEN) == (size_t)-2) \
      ? _PyStatus_ERR("cannot decode " NAME) \
@@ -126,6 +128,7 @@ typedef struct {
     wchar_t *pythonpath_macro;         /* PYTHONPATH macro */
     wchar_t *prefix_macro;             /* PREFIX macro */
     wchar_t *exec_prefix_macro;        /* EXEC_PREFIX macro */
+    wchar_t *vpath_macro;              /* VPATH macro */
 
     wchar_t *lib_python;               /* "lib/pythonX.Y" */
 
@@ -222,7 +225,12 @@ isdir(const wchar_t *filename)
 
 
 /* Add a path component, by appending stuff to buffer.
-   buflen: 'buffer' length in characters including trailing NUL. */
+   buflen: 'buffer' length in characters including trailing NUL.
+
+   If path2 is empty:
+
+   - if path doesn't end with SEP and is not empty, add SEP to path
+   - otherwise, do nothing. */
 static PyStatus
 joinpath(wchar_t *path, const wchar_t *path2, size_t path_len)
 {
@@ -252,6 +260,48 @@ joinpath(wchar_t *path, const wchar_t *path2, size_t path_len)
 }
 
 
+static wchar_t*
+substring(const wchar_t *str, size_t len)
+{
+    wchar_t *substr = PyMem_RawMalloc((len + 1) * sizeof(wchar_t));
+    if (substr == NULL) {
+        return NULL;
+    }
+
+    if (len) {
+        memcpy(substr, str, len * sizeof(wchar_t));
+    }
+    substr[len] = L'\0';
+    return substr;
+}
+
+
+static wchar_t*
+joinpath2(const wchar_t *path, const wchar_t *path2)
+{
+    if (_Py_isabs(path2)) {
+        return _PyMem_RawWcsdup(path2);
+    }
+
+    size_t len = wcslen(path);
+    int add_sep = (len > 0 && path[len - 1] != SEP);
+    len += add_sep;
+    len += wcslen(path2);
+
+    wchar_t *new_path = PyMem_RawMalloc((len + 1) * sizeof(wchar_t));
+    if (new_path == NULL) {
+        return NULL;
+    }
+
+    wcscpy(new_path, path);
+    if (add_sep) {
+        wcscat(new_path, separator);
+    }
+    wcscat(new_path, path2);
+    return new_path;
+}
+
+
 static inline int
 safe_wcscpy(wchar_t *dst, const wchar_t *src, size_t n)
 {
@@ -297,20 +347,22 @@ copy_absolute(wchar_t *abs_path, const wchar_t *path, size_t abs_path_len)
 
 /* path_len: path length in characters including trailing NUL */
 static PyStatus
-absolutize(wchar_t *path, size_t path_len)
+absolutize(wchar_t **path_p)
 {
-    if (_Py_isabs(path)) {
-        return _PyStatus_OK();
-    }
+    assert(!_Py_isabs(*path_p));
 
     wchar_t abs_path[MAXPATHLEN+1];
+    wchar_t *path = *path_p;
+
     PyStatus status = copy_absolute(abs_path, path, Py_ARRAY_LENGTH(abs_path));
     if (_PyStatus_EXCEPTION(status)) {
         return status;
     }
 
-    if (safe_wcscpy(path, abs_path, path_len) < 0) {
-        return PATHLEN_ERR();
+    PyMem_RawFree(*path_p);
+    *path_p = _PyMem_RawWcsdup(abs_path);
+    if (*path_p == NULL) {
+        return _PyStatus_NO_MEMORY();
     }
     return _PyStatus_OK();
 }
@@ -320,33 +372,33 @@ absolutize(wchar_t *path, size_t path_len)
 static PyStatus
 ismodule(const wchar_t *path, int *result)
 {
-    wchar_t filename[MAXPATHLEN+1];
-    size_t filename_len = Py_ARRAY_LENGTH(filename);
-
-    if (safe_wcscpy(filename, path, filename_len) < 0) {
-        return PATHLEN_ERR();
-    }
-
-    PyStatus status = joinpath(filename, LANDMARK, filename_len);
-    if (_PyStatus_EXCEPTION(status)) {
-        return status;
+    wchar_t *filename = joinpath2(path, LANDMARK);
+    if (filename == NULL) {
+        return _PyStatus_NO_MEMORY();
     }
 
     if (isfile(filename)) {
+        PyMem_RawFree(filename);
         *result = 1;
         return _PyStatus_OK();
     }
 
     /* Check for the compiled version of prefix. */
-    if (wcslen(filename) + 2 <= filename_len) {
-        wcscat(filename, L"c");
-        if (isfile(filename)) {
-            *result = 1;
-            return _PyStatus_OK();
-        }
+    size_t len = wcslen(filename);
+    wchar_t *pyc = PyMem_RawMalloc((len + 2) * sizeof(wchar_t));
+    if (pyc == NULL) {
+        PyMem_RawFree(filename);
+        return _PyStatus_NO_MEMORY();
     }
 
-    *result = 0;
+    memcpy(pyc, filename, len * sizeof(wchar_t));
+    pyc[len] = L'c';
+    pyc[len + 1] = L'\0';
+    *result = isfile(pyc);
+
+    PyMem_RawFree(filename);
+    PyMem_RawFree(pyc);
+
     return _PyStatus_OK();
 }
 
@@ -358,24 +410,32 @@ ismodule(const wchar_t *path, int *result)
 
 /* pathlen: 'path' length in characters including trailing NUL */
 static PyStatus
-add_exe_suffix(wchar_t *path, size_t pathlen)
+add_exe_suffix(wchar_t **progpath_p)
 {
+    wchar_t *progpath = *progpath_p;
+
     /* Check for already have an executable suffix */
-    size_t n = wcslen(path);
+    size_t n = wcslen(progpath);
     size_t s = wcslen(EXE_SUFFIX);
-    if (wcsncasecmp(EXE_SUFFIX, path + n - s, s) == 0) {
+    if (wcsncasecmp(EXE_SUFFIX, progpath + n - s, s) == 0) {
         return _PyStatus_OK();
     }
 
-    if (n + s >= pathlen) {
-        return PATHLEN_ERR();
+    wchar_t *progpath2 = PyMem_RawMalloc((n + s + 1) * sizeof(wchar_t));
+    if (progpath2 == NULL) {
+        return _PyStatus_NO_MEMORY();
     }
-    wcsncpy(path + n, EXE_SUFFIX, s);
-    path[n + s] = '\0';
 
-    if (!isxfile(path)) {
-        /* Path that added suffix is invalid: truncate (remove suffix) */
-        path[n] = '\0';
+    memcpy(progpath2, progpath, n * sizeof(wchar_t));
+    memcpy(progpath2 + n, EXE_SUFFIX, s * sizeof(wchar_t));
+    progpath2[n+s] = L'\0';
+
+    if (isxfile(progpath2)) {
+        PyMem_RawFree(*progpath_p);
+        *progpath_p = progpath2;
+    }
+    else {
+        PyMem_RawFree(progpath2);
     }
     return _PyStatus_OK();
 }
@@ -389,10 +449,6 @@ static PyStatus
 search_for_prefix(PyCalculatePath *calculate, _PyPathConfig *pathconfig,
                   wchar_t *prefix, size_t prefix_len, int *found)
 {
-    wchar_t path[MAXPATHLEN+1];
-    memset(path, 0, sizeof(path));
-    size_t path_len = Py_ARRAY_LENGTH(path);
-
     PyStatus status;
 
     /* If PYTHONHOME is set, we believe it unconditionally */
@@ -413,44 +469,46 @@ search_for_prefix(PyCalculatePath *calculate, _PyPathConfig *pathconfig,
         return _PyStatus_OK();
     }
 
-    /* Check to see if argv[0] is in the build directory */
-    if (safe_wcscpy(path, calculate->argv0_path, path_len) < 0) {
-        return PATHLEN_ERR();
-    }
-    status = joinpath(path, L"Modules/Setup.local", path_len);
-    if (_PyStatus_EXCEPTION(status)) {
-        return status;
+    /* Check to see if argv0_path is in the build directory
+
+       Path: <argv0_path> / <BUILD_LANDMARK define> */
+    wchar_t *path = joinpath2(calculate->argv0_path, BUILD_LANDMARK);
+    if (path == NULL) {
+        return _PyStatus_NO_MEMORY();
     }
 
-    if (isfile(path)) {
-        /* Check VPATH to see if argv0_path is in the build directory.
-           VPATH can be empty. */
-        wchar_t *vpath = Py_DecodeLocale(VPATH, NULL);
-        if (vpath != NULL) {
-            /* Path: <argv0_path> / <vpath> / Lib / LANDMARK */
-            if (safe_wcscpy(prefix, calculate->argv0_path, prefix_len) < 0) {
-                return PATHLEN_ERR();
-            }
-            status = joinpath(prefix, vpath, prefix_len);
-            PyMem_RawFree(vpath);
-            if (_PyStatus_EXCEPTION(status)) {
-                return status;
-            }
+    int is_build_dir = isfile(path);
+    PyMem_RawFree(path);
 
-            status = joinpath(prefix, L"Lib", prefix_len);
-            if (_PyStatus_EXCEPTION(status)) {
-                return status;
-            }
+    if (is_build_dir) {
+        /* argv0_path is the build directory (BUILD_LANDMARK exists),
+           now also check LANDMARK using ismodule(). */
 
-            int module;
-            status = ismodule(prefix, &module);
-            if (_PyStatus_EXCEPTION(status)) {
-                return status;
-            }
-            if (module) {
-                *found = -1;
-                return _PyStatus_OK();
-            }
+        /* Path: <argv0_path> / <VPATH macro> / Lib */
+        /* or if VPATH is empty: <argv0_path> / Lib */
+        if (safe_wcscpy(prefix, calculate->argv0_path, prefix_len) < 0) {
+            return PATHLEN_ERR();
+        }
+
+        status = joinpath(prefix, calculate->vpath_macro, prefix_len);
+        if (_PyStatus_EXCEPTION(status)) {
+            return status;
+        }
+
+        status = joinpath(prefix, L"Lib", prefix_len);
+        if (_PyStatus_EXCEPTION(status)) {
+            return status;
+        }
+
+        int module;
+        status = ismodule(prefix, &module);
+        if (_PyStatus_EXCEPTION(status)) {
+            return status;
+        }
+        if (module) {
+            /* BUILD_LANDMARK and LANDMARK found */
+            *found = -1;
+            return _PyStatus_OK();
         }
     }
 
@@ -527,16 +585,14 @@ calculate_prefix(PyCalculatePath *calculate, _PyPathConfig *pathconfig)
             fprintf(stderr,
                 "Could not find platform independent libraries <prefix>\n");
         }
-        if (safe_wcscpy(prefix, calculate->prefix_macro, prefix_len) < 0) {
-            return PATHLEN_ERR();
-        }
-        status = joinpath(prefix, calculate->lib_python, prefix_len);
-        if (_PyStatus_EXCEPTION(status)) {
-            return status;
-        }
+
+        calculate->prefix = joinpath2(calculate->prefix_macro,
+                                      calculate->lib_python);
+    }
+    else {
+        calculate->prefix = _PyMem_RawWcsdup(prefix);
     }
 
-    calculate->prefix = _PyMem_RawWcsdup(prefix);
     if (calculate->prefix == NULL) {
         return _PyStatus_NO_MEMORY();
     }
@@ -553,26 +609,32 @@ calculate_set_prefix(PyCalculatePath *calculate, _PyPathConfig *pathconfig)
      * return the compiled-in defaults instead.
      */
     if (calculate->prefix_found > 0) {
-        wchar_t prefix[MAXPATHLEN+1];
-        memset(prefix, 0, sizeof(prefix));
-
-        wcscpy(prefix, calculate->prefix);
+        wchar_t *prefix = _PyMem_RawWcsdup(calculate->prefix);
+        if (prefix == NULL) {
+            return _PyStatus_NO_MEMORY();
+        }
 
         reduce(prefix);
         reduce(prefix);
-        /* The prefix is the root directory, but reduce() chopped
-         * off the "/". */
-        if (!prefix[0]) {
-            wcscpy(prefix, separator);
+        if (prefix[0]) {
+            pathconfig->prefix = prefix;
+        }
+        else {
+            PyMem_RawFree(prefix);
+
+            /* The prefix is the root directory, but reduce() chopped
+               off the "/". */
+            pathconfig->prefix = _PyMem_RawWcsdup(separator);
+            if (pathconfig->prefix == NULL) {
+                return _PyStatus_NO_MEMORY();
+            }
         }
-        pathconfig->prefix = _PyMem_RawWcsdup(prefix);
     }
     else {
         pathconfig->prefix = _PyMem_RawWcsdup(calculate->prefix_macro);
-    }
-
-    if (pathconfig->prefix == NULL) {
-        return _PyStatus_NO_MEMORY();
+        if (pathconfig->prefix == NULL) {
+            return _PyStatus_NO_MEMORY();
+        }
     }
     return _PyStatus_OK();
 }
@@ -585,28 +647,23 @@ calculate_pybuilddir(const wchar_t *argv0_path,
 {
     PyStatus status;
 
-    wchar_t filename[MAXPATHLEN+1];
-    memset(filename, 0, sizeof(filename));
-    size_t filename_len = Py_ARRAY_LENGTH(filename);
-
     /* 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.
 
        Filename: <argv0_path> / "pybuilddir.txt" */
-    if (safe_wcscpy(filename, argv0_path, filename_len) < 0) {
-        return PATHLEN_ERR();
-    }
-    status = joinpath(filename, L"pybuilddir.txt", filename_len);
-    if (_PyStatus_EXCEPTION(status)) {
-        return status;
+    wchar_t *filename = joinpath2(argv0_path, L"pybuilddir.txt");
+    if (filename == NULL) {
+        return _PyStatus_NO_MEMORY();
     }
 
     if (!isfile(filename)) {
+        PyMem_RawFree(filename);
         return _PyStatus_OK();
     }
 
     FILE *fp = _Py_wfopen(filename, L"rb");
+    PyMem_RawFree(filename);
     if (fp == NULL) {
         errno = 0;
         return _PyStatus_OK();
@@ -756,19 +813,19 @@ calculate_exec_prefix(PyCalculatePath *calculate, _PyPathConfig *pathconfig)
             fprintf(stderr,
                 "Could not find platform dependent libraries <exec_prefix>\n");
         }
-        if (safe_wcscpy(exec_prefix, calculate->exec_prefix_macro, exec_prefix_len) < 0) {
-            return PATHLEN_ERR();
-        }
-        status = joinpath(exec_prefix, L"lib/lib-dynload", exec_prefix_len);
-        if (_PyStatus_EXCEPTION(status)) {
-            return status;
+
+        calculate->exec_prefix = joinpath2(calculate->exec_prefix_macro,
+                                           L"lib/lib-dynload");
+        if (calculate->exec_prefix == NULL) {
+            return _PyStatus_NO_MEMORY();
         }
     }
-
-    /* If we found EXEC_PREFIX do *not* reduce it!  (Yet.) */
-    calculate->exec_prefix = _PyMem_RawWcsdup(exec_prefix);
-    if (calculate->exec_prefix == NULL) {
-        return _PyStatus_NO_MEMORY();
+    else {
+        /* If we found EXEC_PREFIX do *not* reduce it!  (Yet.) */
+        calculate->exec_prefix = _PyMem_RawWcsdup(exec_prefix);
+        if (calculate->exec_prefix == NULL) {
+            return _PyStatus_NO_MEMORY();
+        }
     }
     return _PyStatus_OK();
 }
@@ -779,63 +836,71 @@ calculate_set_exec_prefix(PyCalculatePath *calculate,
                           _PyPathConfig *pathconfig)
 {
     if (calculate->exec_prefix_found > 0) {
-        wchar_t exec_prefix[MAXPATHLEN+1];
-        memset(exec_prefix, 0, sizeof(exec_prefix));
-
-        wcscpy(exec_prefix, calculate->exec_prefix);
+        wchar_t *exec_prefix = _PyMem_RawWcsdup(calculate->exec_prefix);
+        if (exec_prefix == NULL) {
+            return _PyStatus_NO_MEMORY();
+        }
 
         reduce(exec_prefix);
         reduce(exec_prefix);
         reduce(exec_prefix);
-        if (!exec_prefix[0]) {
-            wcscpy(exec_prefix, separator);
-        }
 
-        pathconfig->exec_prefix = _PyMem_RawWcsdup(exec_prefix);
+        if (exec_prefix[0]) {
+            pathconfig->exec_prefix = exec_prefix;
+        }
+        else {
+            /* empty string: use SEP instead */
+            PyMem_RawFree(exec_prefix);
+
+            /* The exec_prefix is the root directory, but reduce() chopped
+               off the "/". */
+            pathconfig->exec_prefix = _PyMem_RawWcsdup(separator);
+            if (pathconfig->exec_prefix == NULL) {
+                return _PyStatus_NO_MEMORY();
+            }
+        }
     }
     else {
         pathconfig->exec_prefix = _PyMem_RawWcsdup(calculate->exec_prefix_macro);
+        if (pathconfig->exec_prefix == NULL) {
+            return _PyStatus_NO_MEMORY();
+        }
     }
-
-    if (pathconfig->exec_prefix == NULL) {
-        return _PyStatus_NO_MEMORY();
-    }
-
     return _PyStatus_OK();
 }
 
 
+/* Similar to shutil.which().
+   If found, write the path into *abs_path_p. */
 static PyStatus
 calculate_which(const wchar_t *path_env, wchar_t *program_name,
-                wchar_t *fullpath, size_t fullpath_len, int *found)
+                wchar_t **abs_path_p)
 {
     while (1) {
         wchar_t *delim = wcschr(path_env, DELIM);
+        wchar_t *abs_path;
 
         if (delim) {
-            size_t len = delim - path_env;
-            if (len >= fullpath_len) {
-                return PATHLEN_ERR();
+            wchar_t *path = substring(path_env, delim - path_env);
+            if (path == NULL) {
+                return _PyStatus_NO_MEMORY();
             }
-            wcsncpy(fullpath, path_env, len);
-            fullpath[len] = '\0';
+            abs_path = joinpath2(path, program_name);
+            PyMem_RawFree(path);
         }
         else {
-            if (safe_wcscpy(fullpath, path_env,
-                        fullpath_len) < 0) {
-                return PATHLEN_ERR();
-            }
+            abs_path = joinpath2(path_env, program_name);
         }
 
-        PyStatus status = joinpath(fullpath, program_name, fullpath_len);
-        if (_PyStatus_EXCEPTION(status)) {
-            return status;
+        if (abs_path == NULL) {
+            return _PyStatus_NO_MEMORY();
         }
 
-        if (isxfile(fullpath)) {
-            *found = 1;
+        if (isxfile(abs_path)) {
+            *abs_path_p = abs_path;
             return _PyStatus_OK();
         }
+        PyMem_RawFree(abs_path);
 
         if (!delim) {
             break;
@@ -850,7 +915,7 @@ calculate_which(const wchar_t *path_env, wchar_t *program_name,
 
 #ifdef __APPLE__
 static PyStatus
-calculate_program_macos(wchar_t *fullpath, size_t fullpath_len, int *found)
+calculate_program_macos(wchar_t **abs_path_p)
 {
     char execpath[MAXPATHLEN + 1];
 #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4
@@ -877,26 +942,20 @@ calculate_program_macos(wchar_t *fullpath, size_t fullpath_len, int *found)
     }
 
     size_t len;
-    wchar_t *path = Py_DecodeLocale(execpath, &len);
-    if (path == NULL) {
+    *abs_path_p = Py_DecodeLocale(execpath, &len);
+    if (*abs_path_p == NULL) {
         return DECODE_LOCALE_ERR("executable path", len);
     }
-    if (safe_wcscpy(fullpath, path, fullpath_len) < 0) {
-        PyMem_RawFree(path);
-        return PATHLEN_ERR();
-    }
-    PyMem_RawFree(path);
-
-    *found = 1;
     return _PyStatus_OK();
 }
 #endif  /* __APPLE__ */
 
 
 static PyStatus
-calculate_program_impl(PyCalculatePath *calculate, _PyPathConfig *pathconfig,
-                       wchar_t *fullpath, size_t fullpath_len)
+calculate_program_impl(PyCalculatePath *calculate, _PyPathConfig *pathconfig)
 {
+    assert(pathconfig->program_full_path == NULL);
+
     PyStatus status;
 
     /* If there is no slash in the argv0 path, then we have to
@@ -905,39 +964,43 @@ calculate_program_impl(PyCalculatePath *calculate, _PyPathConfig *pathconfig,
      * $PATH isn't exported, you lose.
      */
     if (wcschr(pathconfig->program_name, SEP)) {
-        if (safe_wcscpy(fullpath, pathconfig->program_name,
-                        fullpath_len) < 0) {
-            return PATHLEN_ERR();
+        pathconfig->program_full_path = _PyMem_RawWcsdup(pathconfig->program_name);
+        if (pathconfig->program_full_path == NULL) {
+            return _PyStatus_NO_MEMORY();
         }
         return _PyStatus_OK();
     }
 
 #ifdef __APPLE__
-    int found = 0;
-    status = calculate_program_macos(fullpath, fullpath_len, &found);
+    wchar_t *abs_path = NULL;
+    status = calculate_program_macos(&abs_path);
     if (_PyStatus_EXCEPTION(status)) {
         return status;
     }
-    if (found) {
+    if (abs_path) {
+        pathconfig->program_full_path = abs_path;
         return _PyStatus_OK();
     }
 #endif /* __APPLE__ */
 
     if (calculate->path_env) {
-        int found = 0;
+        wchar_t *abs_path = NULL;
         status = calculate_which(calculate->path_env, pathconfig->program_name,
-                                 fullpath, fullpath_len,
-                                 &found);
+                                 &abs_path);
         if (_PyStatus_EXCEPTION(status)) {
             return status;
         }
-        if (found) {
+        if (abs_path) {
+            pathconfig->program_full_path = abs_path;
             return _PyStatus_OK();
         }
     }
 
     /* In the last resort, use an empty string */
-    fullpath[0] = '\0';
+    pathconfig->program_full_path = _PyMem_RawWcsdup(L"");
+    if (pathconfig->program_full_path == NULL) {
+        return _PyStatus_NO_MEMORY();
+    }
     return _PyStatus_OK();
 }
 
@@ -947,23 +1010,18 @@ static PyStatus
 calculate_program(PyCalculatePath *calculate, _PyPathConfig *pathconfig)
 {
     PyStatus status;
-    wchar_t program_full_path[MAXPATHLEN + 1];
-    const size_t program_full_path_len = Py_ARRAY_LENGTH(program_full_path);
-    memset(program_full_path, 0, sizeof(program_full_path));
 
-    status = calculate_program_impl(calculate, pathconfig,
-                                    program_full_path, program_full_path_len);
+    status = calculate_program_impl(calculate, pathconfig);
     if (_PyStatus_EXCEPTION(status)) {
         return status;
     }
 
-    if (program_full_path[0] != '\0') {
+    if (pathconfig->program_full_path[0] != '\0') {
         /* program_full_path is not empty */
 
-        /* Make sure that program_full_path is an absolute path
-           (or an empty string) */
-        if (!_Py_isabs(program_full_path)) {
-            status = absolutize(program_full_path, program_full_path_len);
+        /* Make sure that program_full_path is an absolute path */
+        if (!_Py_isabs(pathconfig->program_full_path)) {
+            status = absolutize(&pathconfig->program_full_path);
             if (_PyStatus_EXCEPTION(status)) {
                 return status;
             }
@@ -975,54 +1033,54 @@ calculate_program(PyCalculatePath *calculate, _PyPathConfig *pathconfig)
          * sys.executable to return the name of a directory under the same
          * path (bpo-28441).
          */
-        status = add_exe_suffix(program_full_path, program_full_path_len);
+        status = add_exe_suffix(&pathconfig->program_full_path);
         if (_PyStatus_EXCEPTION(status)) {
             return status;
         }
 #endif
     }
-
-    pathconfig->program_full_path = _PyMem_RawWcsdup(program_full_path);
-    if (pathconfig->program_full_path == NULL) {
-        return _PyStatus_NO_MEMORY();
-    }
     return _PyStatus_OK();
 }
 
 
 #if HAVE_READLINK
 static PyStatus
-resolve_symlinks(wchar_t *path, size_t path_len)
+resolve_symlinks(wchar_t **path_p)
 {
     wchar_t new_path[MAXPATHLEN + 1];
     const size_t new_path_len = Py_ARRAY_LENGTH(new_path);
-    unsigned int links = 0;
+    unsigned int nlink = 0;
 
     while (1) {
-        int linklen = _Py_wreadlink(path, new_path, new_path_len);
+        int linklen = _Py_wreadlink(*path_p, new_path, new_path_len);
         if (linklen == -1) {
+            /* not a symbolic link: we are done */
             break;
         }
 
         if (_Py_isabs(new_path)) {
-            /* new_path should never be longer than MAXPATHLEN,
-               but extra check does not hurt */
-            if (safe_wcscpy(path, new_path, path_len) < 0) {
-                return PATHLEN_ERR();
+            PyMem_RawFree(*path_p);
+            *path_p = _PyMem_RawWcsdup(new_path);
+            if (*path_p == NULL) {
+                return _PyStatus_NO_MEMORY();
             }
         }
         else {
             /* new_path is relative to path */
-            reduce(path);
-            PyStatus status = joinpath(path, new_path, path_len);
-            if (_PyStatus_EXCEPTION(status)) {
-                return status;
+            reduce(*path_p);
+
+            wchar_t *abs_path = joinpath2(*path_p, new_path);
+            if (abs_path == NULL) {
+                return _PyStatus_NO_MEMORY();
             }
+
+            PyMem_RawFree(*path_p);
+            *path_p = abs_path;
         }
 
-        links++;
+        nlink++;
         /* 40 is the Linux kernel 4.2 limit */
-        if (links >= 40) {
+        if (nlink >= 40) {
             return _PyStatus_ERR("maximum number of symbolic links reached");
         }
     }
@@ -1033,9 +1091,7 @@ resolve_symlinks(wchar_t *path, size_t path_len)
 
 #ifdef WITH_NEXT_FRAMEWORK
 static PyStatus
-calculate_argv0_path_framework(PyCalculatePath *calculate,
-                               const wchar_t *program_full_path,
-                               wchar_t *argv0_path, size_t argv0_path_len)
+calculate_argv0_path_framework(PyCalculatePath *calculate, _PyPathConfig *pathconfig)
 {
     NSModule pythonModule;
 
@@ -1067,37 +1123,47 @@ calculate_argv0_path_framework(PyCalculatePath *calculate,
 
     /* Path: reduce(modPath) / lib_python / LANDMARK */
     PyStatus status;
-    if (safe_wcscpy(argv0_path, wbuf, argv0_path_len) < 0) {
-        status = PATHLEN_ERR();
+
+    wchar_t *parent = _PyMem_RawWcsdup(wbuf);
+    if (parent == NULL) {
+        status = _PyStatus_NO_MEMORY();
         goto done;
     }
-    reduce(argv0_path);
-    status = joinpath(argv0_path, calculate->lib_python, argv0_path_len);
-    if (_PyStatus_EXCEPTION(status)) {
+
+    reduce(parent);
+    wchar_t *lib_python = joinpath2(path, calculate->lib_python);
+    PyMem_RawFree(parent);
+
+    if (lib_python == NULL) {
+        status = _PyStatus_NO_MEMORY();
         goto done;
     }
 
     int module;
-    status = ismodule(argv0_path, &module);
+    status = ismodule(lib_python, &module);
+    PyMem_RawFree(lib_python);
+
     if (_PyStatus_EXCEPTION(status)) {
         goto done;
     }
-    if (module) {
-        /* Use the location of the library as argv0_path */
-        if (safe_wcscpy(argv0_path, wbuf, argv0_path_len) < 0) {
-            status = PATHLEN_ERR();
-            goto done;
-        }
-    }
-    else {
+    if (!module) {
         /* We are in the build directory so use the name of the
            executable - we know that the absolute path is passed */
-        if (safe_wcscpy(argv0_path, program_full_path, argv0_path_len) < 0) {
-            status = PATHLEN_ERR();
+        PyMem_RawFree(*calculate->argv0_path);
+        calculate->argv0_path = _PyMem_RawWcsdup(pathconfig->program_full_path);
+        if (calculate->argv0_path == NULL) {
+            status = _PyStatus_NO_MEMORY();
             goto done;
         }
+
+        status = _PyStatus_OK();
+        goto done;
     }
-    status = _PyStatus_OK();
+
+    /* Use the location of the library as argv0_path */
+    PyMem_RawFree(*calculate->argv0_path);
+    calculate->argv0_path = wbuf
+    return _PyStatus_OK();
 
 done:
     PyMem_RawFree(wbuf);
@@ -1108,35 +1174,77 @@ calculate_argv0_path_framework(PyCalculatePath *calculate,
 
 static PyStatus
 calculate_argv0_path(PyCalculatePath *calculate,
-                     const wchar_t *program_full_path)
+                     _PyPathConfig *pathconfig)
 {
-    wchar_t argv0_path[MAXPATHLEN+1];
-    memset(argv0_path, 0, sizeof(argv0_path));
-    size_t argv0_path_len = Py_ARRAY_LENGTH(argv0_path);
+    PyStatus status;
 
-    if (safe_wcscpy(argv0_path, program_full_path, argv0_path_len) < 0) {
-        return PATHLEN_ERR();
+    calculate->argv0_path = _PyMem_RawWcsdup(pathconfig->program_full_path);
+    if (calculate->argv0_path == NULL) {
+        return _PyStatus_NO_MEMORY();
     }
 
 #ifdef WITH_NEXT_FRAMEWORK
-    PyStatus status;
-    status = calculate_argv0_path_framework(calculate, program_full_path,
-                                            argv0_path, argv0_path_len);
+    status = calculate_argv0_path_framework(calculate, pathconfig);
     if (_PyStatus_EXCEPTION(status)) {
         return status;
     }
 #endif
 
-    resolve_symlinks(argv0_path, argv0_path_len);
+    status = resolve_symlinks(&calculate->argv0_path);
+    if (_PyStatus_EXCEPTION(status)) {
+        return status;
+    }
 
-    reduce(argv0_path);
-    /* At this point, argv0_path is guaranteed to be less than
-       MAXPATHLEN bytes long. */
+    reduce(calculate->argv0_path);
 
-    calculate->argv0_path = _PyMem_RawWcsdup(argv0_path);
-    if (calculate->argv0_path == NULL) {
+    return _PyStatus_OK();
+}
+
+
+static PyStatus
+calculate_open_pyenv(PyCalculatePath *calculate, FILE **env_file_p)
+{
+    *env_file_p = NULL;
+
+    const wchar_t *env_cfg = L"pyvenv.cfg";
+
+    /* Filename: <argv0_path> / "pyvenv.cfg" */
+    wchar_t *filename = joinpath2(calculate->argv0_path, env_cfg);
+    if (filename == NULL) {
+        return _PyStatus_NO_MEMORY();
+    }
+
+    *env_file_p = _Py_wfopen(filename, L"r");
+    PyMem_RawFree(filename);
+
+    if (*env_file_p != NULL) {
+        return _PyStatus_OK();
+
+    }
+
+    /* fopen() failed: reset errno */
+    errno = 0;
+
+    /* Path: <basename(argv0_path)> / "pyvenv.cfg" */
+    wchar_t *parent = _PyMem_RawWcsdup(calculate->argv0_path);
+    if (parent == NULL) {
+        return _PyStatus_NO_MEMORY();
+    }
+    reduce(parent);
+
+    filename = joinpath2(parent, env_cfg);
+    PyMem_RawFree(parent);
+    if (filename == NULL) {
         return _PyStatus_NO_MEMORY();
     }
+
+    *env_file_p = _Py_wfopen(filename, L"r");
+    PyMem_RawFree(filename);
+
+    if (*env_file_p == NULL) {
+        /* fopen() failed: reset errno */
+        errno = 0;
+    }
     return _PyStatus_OK();
 }
 
@@ -1150,53 +1258,28 @@ static PyStatus
 calculate_read_pyenv(PyCalculatePath *calculate)
 {
     PyStatus status;
-    const wchar_t *env_cfg = L"pyvenv.cfg";
-    FILE *env_file;
-
-    wchar_t filename[MAXPATHLEN+1];
-    const size_t filename_len = Py_ARRAY_LENGTH(filename);
-    memset(filename, 0, sizeof(filename));
-
-    /* Filename: <argv0_path_len> / "pyvenv.cfg" */
-    if (safe_wcscpy(filename, calculate->argv0_path, filename_len) < 0) {
-        return PATHLEN_ERR();
-    }
+    FILE *env_file = NULL;
 
-    status = joinpath(filename, env_cfg, filename_len);
+    status = calculate_open_pyenv(calculate, &env_file);
     if (_PyStatus_EXCEPTION(status)) {
         return status;
     }
-    env_file = _Py_wfopen(filename, L"r");
     if (env_file == NULL) {
-        errno = 0;
-
-        /* Filename: <basename(basename(argv0_path_len))> / "pyvenv.cfg" */
-        reduce(filename);
-        reduce(filename);
-        status = joinpath(filename, env_cfg, filename_len);
-        if (_PyStatus_EXCEPTION(status)) {
-            return status;
-        }
-
-        env_file = _Py_wfopen(filename, L"r");
-        if (env_file == NULL) {
-            errno = 0;
-            return _PyStatus_OK();
-        }
+        /* pyvenv.cfg not found */
+        return _PyStatus_OK();
     }
 
     /* Look for a 'home' variable and set argv0_path to it, if found */
-    wchar_t home[MAXPATHLEN+1];
-    memset(home, 0, sizeof(home));
+    wchar_t *home = NULL;
+    status = _Py_FindEnvConfigValue(env_file, L"home", &home);
+    if (_PyStatus_EXCEPTION(status)) {
+        fclose(env_file);
+        return status;
+    }
 
-    if (_Py_FindEnvConfigValue(env_file, L"home",
-                               home, Py_ARRAY_LENGTH(home))) {
+    if (home) {
         PyMem_RawFree(calculate->argv0_path);
-        calculate->argv0_path = _PyMem_RawWcsdup(home);
-        if (calculate->argv0_path == NULL) {
-            fclose(env_file);
-            return _PyStatus_NO_MEMORY();
-         }
+        calculate->argv0_path = home;
     }
     fclose(env_file);
     return _PyStatus_OK();
@@ -1206,39 +1289,34 @@ calculate_read_pyenv(PyCalculatePath *calculate)
 static PyStatus
 calculate_zip_path(PyCalculatePath *calculate)
 {
-    PyStatus status;
-
-    wchar_t zip_path[MAXPATHLEN+1];    /* ".../lib/pythonXY.zip" */
-    memset(zip_path, 0, sizeof(zip_path));
-    size_t zip_path_len = Py_ARRAY_LENGTH(zip_path);
+    const wchar_t *lib_python = L"lib/python00.zip";
 
     if (calculate->prefix_found > 0) {
-        /* Use the reduced prefix returned by Py_GetPrefix() */
-        if (safe_wcscpy(zip_path, calculate->prefix, zip_path_len) < 0) {
-            return PATHLEN_ERR();
+        /* Use the reduced prefix returned by Py_GetPrefix()
+
+           Path: <basename(basename(prefix))> / <lib_python> */
+        wchar_t *parent = _PyMem_RawWcsdup(calculate->prefix);
+        if (parent == NULL) {
+            return _PyStatus_NO_MEMORY();
         }
-        reduce(zip_path);
-        reduce(zip_path);
+        reduce(parent);
+        reduce(parent);
+        calculate->zip_path = joinpath2(parent, lib_python);
+        PyMem_RawFree(parent);
     }
     else {
-        if (safe_wcscpy(zip_path, calculate->prefix_macro, zip_path_len) < 0) {
-            return PATHLEN_ERR();
-        }
-    }
-    status = joinpath(zip_path, L"lib/python00.zip", zip_path_len);
-    if (_PyStatus_EXCEPTION(status)) {
-        return status;
+        calculate->zip_path = joinpath2(calculate->prefix_macro, lib_python);
     }
 
-    /* Replace "00" with version */
-    size_t bufsz = wcslen(zip_path);
-    zip_path[bufsz - 6] = VERSION[0];
-    zip_path[bufsz - 5] = VERSION[2];
-
-    calculate->zip_path = _PyMem_RawWcsdup(zip_path);
     if (calculate->zip_path == NULL) {
         return _PyStatus_NO_MEMORY();
     }
+
+    /* Replace "00" with version */
+    size_t len = wcslen(calculate->zip_path);
+    calculate->zip_path[len - 6] = VERSION[0];
+    calculate->zip_path[len - 5] = VERSION[2];
+
     return _PyStatus_OK();
 }
 
@@ -1344,22 +1422,27 @@ calculate_init(PyCalculatePath *calculate, const PyConfig *config)
         }
     }
 
+    /* Decode macros */
     calculate->pythonpath_macro = Py_DecodeLocale(PYTHONPATH, &len);
     if (!calculate->pythonpath_macro) {
-        return DECODE_LOCALE_ERR("PYTHONPATH define", len);
+        return DECODE_LOCALE_ERR("PYTHONPATH macro", len);
     }
-
     calculate->prefix_macro = Py_DecodeLocale(PREFIX, &len);
     if (!calculate->prefix_macro) {
-        return DECODE_LOCALE_ERR("PREFIX define", len);
+        return DECODE_LOCALE_ERR("PREFIX macro", len);
     }
     calculate->exec_prefix_macro = Py_DecodeLocale(EXEC_PREFIX, &len);
     if (!calculate->exec_prefix_macro) {
-        return DECODE_LOCALE_ERR("EXEC_PREFIX define", len);
+        return DECODE_LOCALE_ERR("EXEC_PREFIX macro", len);
     }
+    calculate->vpath_macro = Py_DecodeLocale(VPATH, &len);
+    if (!calculate->vpath_macro) {
+        return DECODE_LOCALE_ERR("VPATH macro", len);
+    }
+
     calculate->lib_python = Py_DecodeLocale("lib/python" VERSION, &len);
     if (!calculate->lib_python) {
-        return DECODE_LOCALE_ERR("EXEC_PREFIX define", len);
+        return DECODE_LOCALE_ERR("EXEC_PREFIX macro", len);
     }
 
     calculate->warnings = config->pathconfig_warnings;
@@ -1375,6 +1458,7 @@ calculate_free(PyCalculatePath *calculate)
     PyMem_RawFree(calculate->pythonpath_macro);
     PyMem_RawFree(calculate->prefix_macro);
     PyMem_RawFree(calculate->exec_prefix_macro);
+    PyMem_RawFree(calculate->vpath_macro);
     PyMem_RawFree(calculate->lib_python);
     PyMem_RawFree(calculate->path_env);
     PyMem_RawFree(calculate->zip_path);
@@ -1396,7 +1480,7 @@ calculate_path(PyCalculatePath *calculate, _PyPathConfig *pathconfig)
         }
     }
 
-    status = calculate_argv0_path(calculate, pathconfig->program_full_path);
+    status = calculate_argv0_path(calculate, pathconfig);
     if (_PyStatus_EXCEPTION(status)) {
         return status;
     }
@@ -1501,6 +1585,10 @@ _PyPathConfig_Calculate(_PyPathConfig *pathconfig, const PyConfig *config)
         goto done;
     }
 
+    /* program_full_path must an either an empty string or an absolute path */
+    assert(wcslen(pathconfig->program_full_path) == 0
+           || _Py_isabs(pathconfig->program_full_path));
+
     status = _PyStatus_OK();
 
 done:
diff --git a/PC/getpathp.c b/PC/getpathp.c
index b17c002f25c8e..085caf195a992 100644
--- a/PC/getpathp.c
+++ b/PC/getpathp.c
@@ -752,7 +752,7 @@ calculate_pth_file(PyCalculatePath *calculate, _PyPathConfig *pathconfig,
    executable's directory and then in the parent directory.
    If found, open it for use when searching for prefixes.
 */
-static void
+static PyStatus
 calculate_pyvenv_file(PyCalculatePath *calculate,
                       wchar_t *argv0_path, size_t argv0_path_len)
 {
@@ -775,17 +775,23 @@ calculate_pyvenv_file(PyCalculatePath *calculate,
         env_file = _Py_wfopen(filename, L"r");
         if (env_file == NULL) {
             errno = 0;
-            return;
+            return _PyStatus_OK();
         }
     }
 
     /* Look for a 'home' variable and set argv0_path to it, if found */
-    wchar_t home[MAXPATHLEN+1];
-    if (_Py_FindEnvConfigValue(env_file, L"home",
-                               home, Py_ARRAY_LENGTH(home))) {
+    wchar_t *home = NULL;
+    PyStatus status = _Py_FindEnvConfigValue(env_file, L"home", &home);
+    if (_PyStatus_EXCEPTION(status)) {
+        fclose(env_file);
+        return status;
+    }
+    if (home) {
         wcscpy_s(argv0_path, argv0_path_len, home);
+        PyMem_RawFree(home);
     }
     fclose(env_file);
+    return _PyStatus_OK();
 }
 
 
@@ -1022,7 +1028,11 @@ calculate_path(PyCalculatePath *calculate, _PyPathConfig *pathconfig)
         goto done;
     }
 
-    calculate_pyvenv_file(calculate, argv0_path, Py_ARRAY_LENGTH(argv0_path));
+    status = calculate_pyvenv_file(calculate,
+                                   argv0_path, Py_ARRAY_LENGTH(argv0_path));
+    if (_PyStatus_EXCEPTION(status)) {
+        return status;
+    }
 
     /* Calculate zip archive path from DLL or exe path */
     wchar_t zip_path[MAXPATHLEN+1];
diff --git a/Python/pathconfig.c b/Python/pathconfig.c
index 61408e19e3a79..363b7686bc451 100644
--- a/Python/pathconfig.c
+++ b/Python/pathconfig.c
@@ -777,12 +777,17 @@ _PyPathConfig_ComputeSysPath0(const PyWideStringList *argv, PyObject **path0_p)
 #endif
 
 /* Search for a prefix value in an environment file (pyvenv.cfg).
-   If found, copy it into the provided buffer. */
-int
+
+   - If found, copy it into *value_p: string which must be freed by
+     PyMem_RawFree().
+   - If not found, *value_p is set to NULL.
+*/
+PyStatus
 _Py_FindEnvConfigValue(FILE *env_file, const wchar_t *key,
-                       wchar_t *value, size_t value_size)
+                       wchar_t **value_p)
 {
-    int result = 0; /* meaning not found */
+    *value_p = NULL;
+
     char buffer[MAXPATHLEN * 2 + 1];  /* allow extra for key, '=', etc. */
     buffer[Py_ARRAY_LENGTH(buffer)-1] = '\0';
 
@@ -812,18 +817,24 @@ _Py_FindEnvConfigValue(FILE *env_file, const wchar_t *key,
                 if ((tok != NULL) && !wcscmp(tok, L"=")) {
                     tok = WCSTOK(NULL, L"\r\n", &state);
                     if (tok != NULL) {
-                        wcsncpy(value, tok, value_size - 1);
-                        value[value_size - 1] = L'\0';
-                        result = 1;
+                        *value_p = _PyMem_RawWcsdup(tok);
                         PyMem_RawFree(tmpbuffer);
-                        break;
+
+                        if (*value_p == NULL) {
+                            return _PyStatus_NO_MEMORY();
+                        }
+
+                        /* found */
+                        return _PyStatus_OK();
                     }
                 }
             }
             PyMem_RawFree(tmpbuffer);
         }
     }
-    return result;
+
+    /* not found */
+    return _PyStatus_OK();
 }
 
 #ifdef __cplusplus



More information about the Python-checkins mailing list