|
| 1 | +From cfb5d870686a52f9c6d0ee72b1a1c035eb0a19dd Mon Sep 17 00:00:00 2001 |
| 2 | +From: Geoffrey Thomas < [email protected]> |
| 3 | +Date: Tue, 16 Dec 2025 13:43:43 -0500 |
| 4 | +Subject: [PATCH 1/1] [3.10] getpath: Fix library detection and canonicalize |
| 5 | + paths on Linux |
| 6 | +Forwarded: not-needed |
| 7 | + |
| 8 | +This is an implementation of the corresponding patch for 3.11+ |
| 9 | +(patch-python-getpath-library.patch) to the pure-C getpath |
| 10 | +implementation in 3.10. See that patch's commit message for the details |
| 11 | +and motivation. |
| 12 | + |
| 13 | +3.10 getpath appears to loop over parent directories to the calculated |
| 14 | +argv[0] path, so the issue mentioned in the original commit message |
| 15 | +about the extra lib/ segment should not arise. |
| 16 | +--- |
| 17 | + Modules/getpath.c | 77 +++++++++++++++++++++++++++++++++++++++++++++++ |
| 18 | + 1 file changed, 77 insertions(+) |
| 19 | + |
| 20 | +diff --git a/Modules/getpath.c b/Modules/getpath.c |
| 21 | +index ef6dd59a084..41255647bcd 100644 |
| 22 | +--- a/Modules/getpath.c |
| 23 | ++++ b/Modules/getpath.c |
| 24 | +@@ -8,6 +8,7 @@ |
| 25 | + |
| 26 | + #include <sys/types.h> |
| 27 | + #include <string.h> |
| 28 | ++#include <dlfcn.h> |
| 29 | + |
| 30 | + #ifdef __APPLE__ |
| 31 | + # include <mach-o/dyld.h> |
| 32 | +@@ -952,6 +953,15 @@ calculate_program_macos(wchar_t **abs_path_p) |
| 33 | + #endif /* __APPLE__ */ |
| 34 | + |
| 35 | + |
| 36 | ++static void |
| 37 | ++fclose_cleanup(FILE **pf) { |
| 38 | ++ if (*pf) { |
| 39 | ++ fclose(*pf); |
| 40 | ++ *pf = NULL; |
| 41 | ++ } |
| 42 | ++} |
| 43 | ++ |
| 44 | ++ |
| 45 | + static PyStatus |
| 46 | + calculate_program_impl(PyCalculatePath *calculate, _PyPathConfig *pathconfig) |
| 47 | + { |
| 48 | +@@ -959,6 +969,73 @@ calculate_program_impl(PyCalculatePath *calculate, _PyPathConfig *pathconfig) |
| 49 | + |
| 50 | + PyStatus status; |
| 51 | + |
| 52 | ++ const void *target = __builtin_extract_return_addr( |
| 53 | ++ __builtin_return_address(0) |
| 54 | ++ ); |
| 55 | ++ size_t len; |
| 56 | ++ |
| 57 | ++#ifdef __linux__ |
| 58 | ++ /* Linux libcs do not reliably report the realpath in dladdr dli_fname and |
| 59 | ++ * sometimes return relative paths, especially if the returned object is |
| 60 | ++ * the main program itself. However, /proc/self/maps will give absolute |
| 61 | ++ * realpaths (from the kernel, for the same reason that /proc/self/exe is |
| 62 | ++ * canonical), so try to parse and look it up there. (dyld seems to |
| 63 | ++ * reliably report the canonical path, so doing this matches the behavior |
| 64 | ++ * on macOS.) */ |
| 65 | ++ |
| 66 | ++ __attribute__((cleanup(fclose_cleanup))) |
| 67 | ++ FILE *maps = fopen("/proc/self/maps", "r"); |
| 68 | ++ if (maps != NULL) { |
| 69 | ++ /* See implementation in fs/proc/task_mmu.c for spacing. The pathname |
| 70 | ++ * is the last field and has any \n characters escaped, so we can read |
| 71 | ++ * until \n. Note that the filename may have " (deleted)" appended; |
| 72 | ++ * we don't bother to handle that specially as the only user of this |
| 73 | ++ * value calls dirname() anyway. |
| 74 | ++ * TODO(geofft): Consider using PROCMAP_QUERY if supported. |
| 75 | ++ */ |
| 76 | ++ uintptr_t low, high; |
| 77 | ++ char rest[PATH_MAX + 1]; |
| 78 | ++ while (fscanf(maps, "%lx-%lx %*s %*s %*s %*s", &low, &high) == 2) { |
| 79 | ++ if (fgets(rest, PATH_MAX + 1, maps) == NULL) { |
| 80 | ++ break; |
| 81 | ++ } |
| 82 | ++ size_t restlen = strlen(rest); |
| 83 | ++ if (restlen >= PATH_MAX) { |
| 84 | ++ // If the line is too long our parsing will be out of sync. |
| 85 | ++ break; |
| 86 | ++ } |
| 87 | ++ if (restlen > 0 && rest[restlen - 1] == '\n') { |
| 88 | ++ rest[restlen - 1] = '\0'; |
| 89 | ++ } else { |
| 90 | ++ // EOF or error before we got to end of line. |
| 91 | ++ break; |
| 92 | ++ } |
| 93 | ++ |
| 94 | ++ if (low <= (uintptr_t)target && (uintptr_t)target < high) { |
| 95 | ++ // Skip past padding spaces in the filename. |
| 96 | ++ const char *filename = rest + strspn(rest, " "); |
| 97 | ++ if (filename[0] == '/') { |
| 98 | ++ pathconfig->program_full_path = Py_DecodeLocale(filename, &len); |
| 99 | ++ if (pathconfig->program_full_path == NULL) { |
| 100 | ++ return DECODE_LOCALE_ERR("mapped code path", len); |
| 101 | ++ } |
| 102 | ++ return _PyStatus_OK(); |
| 103 | ++ } |
| 104 | ++ break; |
| 105 | ++ } |
| 106 | ++ } |
| 107 | ++ } |
| 108 | ++#endif |
| 109 | ++ |
| 110 | ++ Dl_info libpython_info; |
| 111 | ++ if (dladdr(target, &libpython_info) && libpython_info.dli_fname) { |
| 112 | ++ pathconfig->program_full_path = Py_DecodeLocale(libpython_info.dli_fname, &len); |
| 113 | ++ if (pathconfig->program_full_path == NULL) { |
| 114 | ++ return DECODE_LOCALE_ERR("shared object path", len); |
| 115 | ++ } |
| 116 | ++ return _PyStatus_OK(); |
| 117 | ++ } |
| 118 | ++ |
| 119 | + /* If there is no slash in the argv0 path, then we have to |
| 120 | + * assume python is on the user's $PATH, since there's no |
| 121 | + * other way to find a directory to start the search from. If |
| 122 | +-- |
| 123 | +2.50.1 (Apple Git-155) |
| 124 | + |
0 commit comments