]> granicus.if.org Git - python/commitdiff
bpo-38353: Add subfunctions to getpath.c (GH-16572)
authorVictor Stinner <vstinner@python.org>
Fri, 4 Oct 2019 00:22:39 +0000 (02:22 +0200)
committerGitHub <noreply@github.com>
Fri, 4 Oct 2019 00:22:39 +0000 (02:22 +0200)
Following symbolic links is now limited to 40 attempts, just to
prevent loops.

Add subfunctions:

* Add resolve_symlinks()
* Add calculate_argv0_path_framework()
* Add calculate_which()
* Add calculate_program_macos()

Fix also _Py_wreadlink(): readlink() result type is Py_ssize_t, not
int.

Modules/getpath.c
Python/fileutils.c

index fca87c7b0ee93f672c25f3a8f819ae58b24b6e57..7a66eee881361423322d6f8fe88ee97bf2cacc1f 100644 (file)
@@ -768,21 +768,98 @@ calculate_set_exec_prefix(PyCalculatePath *calculate,
 
 
 static PyStatus
-calculate_program_full_path(PyCalculatePath *calculate, _PyPathConfig *pathconfig)
+calculate_which(const wchar_t *path_env, wchar_t *program_name,
+                wchar_t *fullpath, size_t fullpath_len, int *found)
 {
-    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));
+    while (1) {
+        wchar_t *delim = wcschr(path_env, DELIM);
+
+        if (delim) {
+            size_t len = delim - path_env;
+            if (len >= fullpath_len) {
+                return PATHLEN_ERR();
+            }
+            wcsncpy(fullpath, path_env, len);
+            fullpath[len] = '\0';
+        }
+        else {
+            if (safe_wcscpy(fullpath, path_env,
+                        fullpath_len) < 0) {
+                return PATHLEN_ERR();
+            }
+        }
+
+        PyStatus status = joinpath(fullpath, program_name, fullpath_len);
+        if (_PyStatus_EXCEPTION(status)) {
+            return status;
+        }
+
+        if (isxfile(fullpath)) {
+            *found = 1;
+            return _PyStatus_OK();
+        }
+
+        if (!delim) {
+            break;
+        }
+        path_env = delim + 1;
+    }
+
+    /* not found */
+    return _PyStatus_OK();
+}
+
 
 #ifdef __APPLE__
+static PyStatus
+calculate_program_macos(wchar_t *fullpath, size_t fullpath_len, int *found)
+{
     char execpath[MAXPATHLEN + 1];
 #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4
     uint32_t nsexeclength = Py_ARRAY_LENGTH(execpath) - 1;
 #else
     unsigned long nsexeclength = Py_ARRAY_LENGTH(execpath) - 1;
 #endif
-#endif
+
+    /* 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.
+       If /opt/python2.3/bin isn't in your path, or is near the end,
+       this algorithm may incorrectly find /usr/bin/python. To work
+       around this, we can use _NSGetExecutablePath to get a better
+       hint of what the intended interpreter was, although this
+       will fail if a relative path was used. but in that case,
+       absolutize() should help us out below
+     */
+    if (_NSGetExecutablePath(execpath, &nsexeclength) != 0
+        || (wchar_t)execpath[0] != SEP)
+    {
+        /* _NSGetExecutablePath() failed or the path is relative */
+        return _PyStatus_OK();
+    }
+
+    size_t len;
+    wchar_t *path = Py_DecodeLocale(execpath, &len);
+    if (path == 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)
+{
+    PyStatus status;
 
     /* 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
@@ -790,96 +867,82 @@ calculate_program_full_path(PyCalculatePath *calculate, _PyPathConfig *pathconfi
      * $PATH isn't exported, you lose.
      */
     if (wcschr(pathconfig->program_name, SEP)) {
-        if (safe_wcscpy(program_full_path, pathconfig->program_name,
-                        program_full_path_len) < 0) {
+        if (safe_wcscpy(fullpath, pathconfig->program_name,
+                        fullpath_len) < 0) {
             return PATHLEN_ERR();
         }
+        return _PyStatus_OK();
     }
+
 #ifdef __APPLE__
-     /* 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.
-      * If /opt/python2.3/bin isn't in your path, or is near the end,
-      * this algorithm may incorrectly find /usr/bin/python. To work
-      * around this, we can use _NSGetExecutablePath to get a better
-      * hint of what the intended interpreter was, although this
-      * will fail if a relative path was used. but in that case,
-      * absolutize() should help us out below
-      */
-    else if(0 == _NSGetExecutablePath(execpath, &nsexeclength) &&
-            (wchar_t)execpath[0] == SEP)
-    {
-        size_t len;
-        wchar_t *path = Py_DecodeLocale(execpath, &len);
-        if (path == NULL) {
-            return DECODE_LOCALE_ERR("executable path", len);
+    int found = 0;
+    status = calculate_program_macos(fullpath, fullpath_len, &found);
+    if (_PyStatus_EXCEPTION(status)) {
+        return status;
+    }
+    if (found) {
+        return _PyStatus_OK();
+    }
+#endif /* __APPLE__ */
+
+    if (calculate->path_env) {
+        int found = 0;
+        status = calculate_which(calculate->path_env, pathconfig->program_name,
+                                 fullpath, fullpath_len,
+                                 &found);
+        if (_PyStatus_EXCEPTION(status)) {
+            return status;
         }
-        if (safe_wcscpy(program_full_path, path, program_full_path_len) < 0) {
-            PyMem_RawFree(path);
-            return PATHLEN_ERR();
+        if (found) {
+            return _PyStatus_OK();
         }
-        PyMem_RawFree(path);
     }
-#endif /* __APPLE__ */
-    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 >= program_full_path_len) {
-                    return PATHLEN_ERR();
-                }
-                wcsncpy(program_full_path, path, len);
-                program_full_path[len] = '\0';
-            }
-            else {
-                if (safe_wcscpy(program_full_path, path,
-                                program_full_path_len) < 0) {
-                    return PATHLEN_ERR();
-                }
-            }
 
-            status = joinpath(program_full_path, pathconfig->program_name,
-                              program_full_path_len);
-            if (_PyStatus_EXCEPTION(status)) {
-                return status;
-            }
+    /* In the last resort, use an empty string */
+    fullpath[0] = '\0';
+    return _PyStatus_OK();
+}
 
-            if (isxfile(program_full_path)) {
-                break;
-            }
 
-            if (!delim) {
-                program_full_path[0] = L'\0';
-                break;
-            }
-            path = delim + 1;
-        }
-    }
-    else {
-        program_full_path[0] = '\0';
+/* Calculate pathconfig->program_full_path */
+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);
+    if (_PyStatus_EXCEPTION(status)) {
+        return status;
     }
-    if (!_Py_isabs(program_full_path) && program_full_path[0] != '\0') {
-        status = absolutize(program_full_path, program_full_path_len);
-        if (_PyStatus_EXCEPTION(status)) {
-            return status;
+
+    if (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);
+            if (_PyStatus_EXCEPTION(status)) {
+                return status;
+            }
         }
-    }
+
 #if defined(__CYGWIN__) || defined(__MINGW32__)
-    /* For these platforms it is necessary to ensure that the .exe suffix
-     * is appended to the filename, otherwise there is potential for
-     * sys.executable to return the name of a directory under the same
-     * path (bpo-28441).
-     */
-    if (program_full_path[0] != '\0') {
+        /* For these platforms it is necessary to ensure that the .exe suffix
+         * is appended to the filename, otherwise there is potential for
+         * 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);
         if (_PyStatus_EXCEPTION(status)) {
             return status;
         }
-    }
 #endif
+    }
 
     pathconfig->program_full_path = _PyMem_RawWcsdup(program_full_path);
     if (pathconfig->program_full_path == NULL) {
@@ -889,97 +952,140 @@ calculate_program_full_path(PyCalculatePath *calculate, _PyPathConfig *pathconfi
 }
 
 
+#if HAVE_READLINK
 static PyStatus
-calculate_argv0_path(PyCalculatePath *calculate, const wchar_t *program_full_path,
-                     wchar_t *argv0_path, size_t argv0_path_len)
+resolve_symlinks(wchar_t *path, size_t path_len)
 {
-    if (safe_wcscpy(argv0_path, program_full_path, argv0_path_len) < 0) {
-        return PATHLEN_ERR();
+    wchar_t new_path[MAXPATHLEN + 1];
+    const size_t new_path_len = Py_ARRAY_LENGTH(new_path);
+    unsigned int links = 0;
+
+    while (1) {
+        int linklen = _Py_wreadlink(path, new_path, new_path_len);
+        if (linklen == -1) {
+            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();
+            }
+        }
+        else {
+            /* new_path is relative to path */
+            reduce(path);
+            PyStatus status = joinpath(path, new_path, path_len);
+            if (_PyStatus_EXCEPTION(status)) {
+                return status;
+            }
+        }
+
+        links++;
+        /* 40 is the Linux kernel 4.2 limit */
+        if (links >= 40) {
+            return _PyStatus_ERR("maximum number of symbolic links reached");
+        }
     }
+    return _PyStatus_OK();
+}
+#endif /* HAVE_READLINK */
+
 
 #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)
+{
     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
-    ** be outside of the framework. Except when we're in the build directory...
-    */
+       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
+       be outside of the framework. Except when we're in the build
+       directory... */
     pythonModule = NSModuleForSymbol(NSLookupAndBindSymbol("_Py_Initialize"));
+
     /* Use dylib functions to find out where the framework was loaded from */
     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
-        ** build directory is incomplete, it only has the .dylib and a few
-        ** needed symlinks, it doesn't have the Lib directories and such.
-        ** If we're running with the framework from the build directory we must
-        ** be running the interpreter in the build directory, so we use the
-        ** build-directory-specific logic to find Lib and such.
-        */
-        PyStatus status;
-        size_t len;
-        wchar_t* wbuf = Py_DecodeLocale(modPath, &len);
-        if (wbuf == NULL) {
-            return DECODE_LOCALE_ERR("framework location", len);
-        }
+    if (modPath == NULL) {
+        return _PyStatus_OK();
+    }
+
+    /* We're in a framework.
+       See if we might be in the build directory. The framework in the
+       build directory is incomplete, it only has the .dylib and a few
+       needed symlinks, it doesn't have the Lib directories and such.
+       If we're running with the framework from the build directory we must
+       be running the interpreter in the build directory, so we use the
+       build-directory-specific logic to find Lib and such. */
+    size_t len;
+    wchar_t* wbuf = Py_DecodeLocale(modPath, &len);
+    if (wbuf == NULL) {
+        return DECODE_LOCALE_ERR("framework location", len);
+    }
+
+    /* Path: reduce(modPath) / lib_python / LANDMARK */
+    PyStatus status;
+    if (safe_wcscpy(argv0_path, wbuf, argv0_path_len) < 0) {
+        status = PATHLEN_ERR();
+        goto done;
+    }
+    reduce(argv0_path);
+    status = joinpath(argv0_path, calculate->lib_python, argv0_path_len);
+    if (_PyStatus_EXCEPTION(status)) {
+        goto done;
+    }
+    status = joinpath(argv0_path, LANDMARK, argv0_path_len);
+    if (_PyStatus_EXCEPTION(status)) {
+        goto done;
+    }
 
+    if (ismodule(argv0_path, Py_ARRAY_LENGTH(argv0_path))) {
+        /* Use the location of the library as argv0_path */
         if (safe_wcscpy(argv0_path, wbuf, argv0_path_len) < 0) {
-            return PATHLEN_ERR();
-        }
-        reduce(argv0_path);
-        status = joinpath(argv0_path, calculate->lib_python, argv0_path_len);
-        if (_PyStatus_EXCEPTION(status)) {
-            PyMem_RawFree(wbuf);
-            return status;
-        }
-        status = joinpath(argv0_path, LANDMARK, argv0_path_len);
-        if (_PyStatus_EXCEPTION(status)) {
-            PyMem_RawFree(wbuf);
-            return status;
-        }
-        if (!ismodule(argv0_path, Py_ARRAY_LENGTH(argv0_path))) {
-            /* 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) {
-                return PATHLEN_ERR();
-            }
+            status = PATHLEN_ERR();
+            goto done;
         }
-        else {
-            /* Use the location of the library as the program_full_path */
-            if (safe_wcscpy(argv0_path, wbuf, argv0_path_len) < 0) {
-                return PATHLEN_ERR();
-            }
+    }
+    else {
+        /* 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();
+            goto done;
         }
-        PyMem_RawFree(wbuf);
     }
+    status = _PyStatus_OK();
+
+done:
+    PyMem_RawFree(wbuf);
+    return status;
+}
 #endif
 
-#if HAVE_READLINK
-    wchar_t tmpbuffer[MAXPATHLEN + 1];
-    const size_t buflen = Py_ARRAY_LENGTH(tmpbuffer);
-    int linklen = _Py_wreadlink(argv0_path, tmpbuffer, buflen);
-    while (linklen != -1) {
-        if (_Py_isabs(tmpbuffer)) {
-            /* tmpbuffer should never be longer than MAXPATHLEN,
-               but extra check does not hurt */
-            if (safe_wcscpy(argv0_path, tmpbuffer, argv0_path_len) < 0) {
-                return PATHLEN_ERR();
-            }
-        }
-        else {
-            /* Interpret relative to program_full_path */
-            PyStatus status;
-            reduce(argv0_path);
-            status = joinpath(argv0_path, tmpbuffer, argv0_path_len);
-            if (_PyStatus_EXCEPTION(status)) {
-                return status;
-            }
-        }
-        linklen = _Py_wreadlink(argv0_path, tmpbuffer, buflen);
+
+static PyStatus
+calculate_argv0_path(PyCalculatePath *calculate,
+                     const wchar_t *program_full_path,
+                     wchar_t *argv0_path, size_t argv0_path_len)
+{
+    if (safe_wcscpy(argv0_path, program_full_path, argv0_path_len) < 0) {
+        return PATHLEN_ERR();
     }
-#endif /* HAVE_READLINK */
+
+#ifdef WITH_NEXT_FRAMEWORK
+    PyStatus status;
+    status = calculate_argv0_path_framework(calculate, program_full_path,
+                                            argv0_path, argv0_path_len);
+    if (_PyStatus_EXCEPTION(status)) {
+        return status;
+    }
+#endif
+
+    resolve_symlinks(argv0_path, argv0_path_len);
 
     reduce(argv0_path);
     /* At this point, argv0_path is guaranteed to be less than
@@ -1226,7 +1332,7 @@ calculate_path(PyCalculatePath *calculate, _PyPathConfig *pathconfig)
     PyStatus status;
 
     if (pathconfig->program_full_path == NULL) {
-        status = calculate_program_full_path(calculate, pathconfig);
+        status = calculate_program(calculate, pathconfig);
         if (_PyStatus_EXCEPTION(status)) {
             return status;
         }
@@ -1275,8 +1381,8 @@ calculate_path(PyCalculatePath *calculate, _PyPathConfig *pathconfig)
         return status;
     }
 
-    if ((!calculate->prefix_found || !calculate->exec_prefix_found) &&
-        calculate->warnings)
+    if ((!calculate->prefix_found || !calculate->exec_prefix_found)
+        && calculate->warnings)
     {
         fprintf(stderr,
                 "Consider setting $PYTHONHOME to <prefix>[:<exec_prefix>]\n");
index 0c05424143afb5a3406b737e09869375eacdee76..6345553f484c77a147bad1f61f9e03dfbb72dee9 100644 (file)
@@ -1668,8 +1668,9 @@ _Py_wreadlink(const wchar_t *path, wchar_t *buf, size_t buflen)
 {
     char *cpath;
     char cbuf[MAXPATHLEN];
+    size_t cbuf_len = Py_ARRAY_LENGTH(cbuf);
     wchar_t *wbuf;
-    int res;
+    Py_ssize_t res;
     size_t r1;
 
     cpath = _Py_EncodeLocaleRaw(path, NULL);
@@ -1677,11 +1678,12 @@ _Py_wreadlink(const wchar_t *path, wchar_t *buf, size_t buflen)
         errno = EINVAL;
         return -1;
     }
-    res = (int)readlink(cpath, cbuf, Py_ARRAY_LENGTH(cbuf));
+    res = readlink(cpath, cbuf, cbuf_len);
     PyMem_RawFree(cpath);
-    if (res == -1)
+    if (res == -1) {
         return -1;
-    if (res == Py_ARRAY_LENGTH(cbuf)) {
+    }
+    if ((size_t)res == cbuf_len) {
         errno = EINVAL;
         return -1;
     }