Skip to content

Commit eeac82f

Browse files
committed
Implement the getpath library name patch on 3.10
1 parent a2135f1 commit eeac82f

File tree

3 files changed

+126
-4
lines changed

3 files changed

+126
-4
lines changed

cpython-unix/build-cpython.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -629,6 +629,8 @@ if [[ -n "${PYTHON_MEETS_MINIMUM_VERSION_3_11}" ]]; then
629629
patch -p1 -i "${ROOT}/patch-python-getpath-backport-${PYTHON_MAJMIN_VERSION}.patch"
630630
fi
631631
patch -p1 -i "${ROOT}/patch-python-getpath-library.patch"
632+
else
633+
patch -p1 -i "${ROOT}/patch-python-getpath-library-3.10.patch"
632634
fi
633635

634636
# Another, similar change to getpath: When reading inside a venv use the base_executable path to
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
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+

src/verify_distribution.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -272,10 +272,6 @@ def assertLibc(value):
272272

273273
assertLibc(importlib.machinery.EXTENSION_SUFFIXES[0])
274274

275-
@unittest.skipIf(
276-
sys.version_info[:2] < (3, 11),
277-
"not yet implemented",
278-
)
279275
@unittest.skipIf(os.name == "nt", "no symlinks or argv[0] on Windows")
280276
def test_getpath(self):
281277
def assertPythonWorks(path: Path, argv0: str = None):

0 commit comments

Comments
 (0)