https://github.com/python/cpython/commit/15dd7356f300733b9c3fcc35f0d75e61a6ef3e05
commit: 15dd7356f300733b9c3fcc35f0d75e61a6ef3e05
branch: main
author: Victor Stinner <[email protected]>
committer: vstinner <[email protected]>
date: 2026-10-07T20:31:27Z
summary:

gh-158893: Add internal _Py_strerror() function (#158982)

Add a new internal _Py_strerror() function to Python/fileutils.c. It
uses strerror_r() if available, or use strerror() otherwise.

Replace all strerror(code) calls with _Py_strerror(code).

files:
M Include/internal/pycore_fileutils.h
M Modules/_remote_debugging/subprocess.c
M Modules/_remote_debugging/threads.c
M Modules/posixmodule.c
M Python/errors.c
M Python/fileutils.c

diff --git a/Include/internal/pycore_fileutils.h 
b/Include/internal/pycore_fileutils.h
index a765eb5fe2d322..a5fe5206477d05 100644
--- a/Include/internal/pycore_fileutils.h
+++ b/Include/internal/pycore_fileutils.h
@@ -321,6 +321,9 @@ extern int _Py_GetTicksPerSecond(long *ticks_per_second);
 // Export for '_testcapi' shared extension
 PyAPI_FUNC(int) _Py_IsValidFD(int fd);
 
+// Export for '_remote_debugging' shared extension
+PyAPI_FUNC(PyObject*) _Py_strerror(int code);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/Modules/_remote_debugging/subprocess.c 
b/Modules/_remote_debugging/subprocess.c
index 1388a75a95c47b..eeb9693cef9222 100644
--- a/Modules/_remote_debugging/subprocess.c
+++ b/Modules/_remote_debugging/subprocess.c
@@ -6,6 +6,7 @@
  
******************************************************************************/
 
 #include "_remote_debugging.h"
+#include "pycore_fileutils.h"     // _Py_strerror()
 
 #ifndef MS_WINDOWS
 #include <unistd.h>
@@ -229,9 +230,13 @@ get_child_pids_platform(pid_t target_pid, int recursive, 
pid_array_t *result)
         if (entry == NULL) {
             if (errno != 0) {
                 int err = errno;
-                _set_debug_oserror_from_errno_with_filename(err, "/proc",
-                    "Failed to read process directory '/proc': %s",
-                    strerror(err));
+                PyObject *message = _Py_strerror(err);
+                if (message != NULL) {
+                    _set_debug_oserror_from_errno_with_filename(err, "/proc",
+                        "Failed to read process directory '/proc': %S",
+                        message);
+                    Py_DECREF(message);
+                }
                 goto done;
             }
             break;
@@ -259,9 +264,13 @@ get_child_pids_platform(pid_t target_pid, int recursive, 
pid_array_t *result)
     if (closedir(proc_dir) != 0) {
         int err = errno;
         proc_dir = NULL;
-        _set_debug_oserror_from_errno_with_filename(err, "/proc",
-            "Failed to close process directory '/proc': %s",
-            strerror(err));
+        PyObject *message = _Py_strerror(err);
+        if (message != NULL) {
+            _set_debug_oserror_from_errno_with_filename(err, "/proc",
+                "Failed to close process directory '/proc': %S",
+                message);
+            Py_DECREF(message);
+        }
         goto done;
     }
     proc_dir = NULL;
diff --git a/Modules/_remote_debugging/threads.c 
b/Modules/_remote_debugging/threads.c
index 198134fe6cfbea..120065022ddc8e 100644
--- a/Modules/_remote_debugging/threads.c
+++ b/Modules/_remote_debugging/threads.c
@@ -6,6 +6,7 @@
  
******************************************************************************/
 
 #include "_remote_debugging.h"
+#include "pycore_fileutils.h"     // _Py_strerror()
 
 #ifndef MS_WINDOWS
 #include <unistd.h>
@@ -716,9 +717,13 @@ read_thread_ids(RemoteUnwinderObject *unwinder, 
_Py_RemoteDebug_ThreadsState *st
                 int err = errno;
                 closedir(dir);
                 _Py_RemoteDebug_InitThreadsState(unwinder, st);
-                _set_debug_oserror_from_errno_with_filename(err, task_path,
-                    "Failed to read process task directory '%s': %s",
-                    task_path, strerror(err));
+                PyObject *message = _Py_strerror(err);
+                if (message != NULL) {
+                    _set_debug_oserror_from_errno_with_filename(err, task_path,
+                        "Failed to read process task directory '%s': %S",
+                        task_path, message);
+                    Py_DECREF(message);
+                }
                 return -1;
             }
             break;
@@ -749,9 +754,13 @@ read_thread_ids(RemoteUnwinderObject *unwinder, 
_Py_RemoteDebug_ThreadsState *st
     if (closedir(dir) != 0) {
         int err = errno;
         _Py_RemoteDebug_InitThreadsState(unwinder, st);
-        _set_debug_oserror_from_errno_with_filename(err, task_path,
-            "Failed to close process task directory '%s': %s",
-            task_path, strerror(err));
+        PyObject *message = _Py_strerror(err);
+        if (message != NULL) {
+            _set_debug_oserror_from_errno_with_filename(err, task_path,
+                "Failed to close process task directory '%s': %S",
+                task_path, message);
+            Py_DECREF(message);
+        }
         return -1;
     }
     st->tids = unwinder->thread_tids;
@@ -816,8 +825,12 @@ _Py_RemoteDebug_StopAllThreads(RemoteUnwinderObject 
*unwinder, _Py_RemoteDebug_T
         }
         if (ret < 0) {
             detach_threads(st, seized);
-            _set_debug_oserror_from_errno(err,
-                "Failed to seize thread %d: %s", tid, strerror(err));
+            PyObject *message = _Py_strerror(err);
+            if (message != NULL) {
+                _set_debug_oserror_from_errno(err,
+                    "Failed to seize thread %d: %S", tid, message);
+                Py_DECREF(message);
+            }
             _Py_RemoteDebug_InitThreadsState(unwinder, st);
             return -1;
         }
@@ -827,8 +840,12 @@ _Py_RemoteDebug_StopAllThreads(RemoteUnwinderObject 
*unwinder, _Py_RemoteDebug_T
             err = errno;
             if (err != ESRCH) {
                 detach_threads(st, seized);
-                _set_debug_oserror_from_errno(err,
-                    "Failed to interrupt thread %d: %s", tid, strerror(err));
+                PyObject *message = _Py_strerror(err);
+                if (message != NULL) {
+                    _set_debug_oserror_from_errno(err,
+                        "Failed to interrupt thread %d: %S", tid, message);
+                    Py_DECREF(message);
+                }
                 _Py_RemoteDebug_InitThreadsState(unwinder, st);
                 return -1;
             }
@@ -839,8 +856,12 @@ _Py_RemoteDebug_StopAllThreads(RemoteUnwinderObject 
*unwinder, _Py_RemoteDebug_T
             err = errno;
             if (err != ECHILD && err != ESRCH) {
                 detach_threads(st, seized);
-                _set_debug_oserror_from_errno(err,
-                    "waitpid failed for thread %d: %s", tid, strerror(err));
+                PyObject *message = _Py_strerror(err);
+                if (message != NULL) {
+                    _set_debug_oserror_from_errno(err,
+                        "waitpid failed for thread %d: %S", tid, message);
+                    Py_DECREF(message);
+                }
                 _Py_RemoteDebug_InitThreadsState(unwinder, st);
                 return -1;
             }
diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c
index 7eb17164113756..028270e7b841fd 100644
--- a/Modules/posixmodule.c
+++ b/Modules/posixmodule.c
@@ -13923,86 +13923,7 @@ static PyObject *
 os_strerror_impl(PyObject *module, int code)
 /*[clinic end generated code: output=baebf09fa02a78f2 input=75a8673d97915a91]*/
 {
-#ifdef _Py_HAVE_STRERROR_R
-   // Check which strerror_r() API is used
-#  if defined(__GLIBC__) && !((_POSIX_C_SOURCE >= 200112L) && 
!defined(_GNU_SOURCE))
-#    define Py_STRERROR_R_GNU
-#  elif defined(__ANDROID__) && defined(_GNU_SOURCE)
-#    define Py_STRERROR_R_GNU
-#  endif
-#endif
-
-#ifdef Py_STRERROR_R_GNU
-    // Implementation for the GNU flavor of strerror_r()
-
-    // On Linux, the longest translated strerror() message is 86 bytes
-    // (including the NUL byte).
-    char buffer[100];
-    char *message = strerror_r(code, buffer, Py_ARRAY_LENGTH(buffer));
-    // The strerror_r() GNU flavor doesn't provide a way to check if the error
-    // message was truncated or not.
-    //
-    // When the buffer is used, a trailing NUL byte is always written.
-    assert(message != buffer || memchr(buffer, 0, Py_ARRAY_LENGTH(buffer)) != 
NULL);
-    return PyUnicode_DecodeLocale(message, "surrogateescape");
-
-#elif defined(_Py_HAVE_STRERROR_R)
-    // Implementation for the XSI-compliant flavor of strerror_r()
-
-    // On Linux and FreeBSD, the longest translated strerror() message is 86
-    // bytes (including the NUL byte).
-    char small_buffer[100];
-    size_t buflen = Py_ARRAY_LENGTH(small_buffer);
-    char *buffer = NULL;
-#ifndef NDEBUG
-    // Make sure that strerror_r() writes a trailing null byte
-    small_buffer[buflen - 1] = '#';
-#endif
-    int len = strerror_r(code, small_buffer, buflen);
-    if (len == ERANGE) {
-        while (len == ERANGE) {
-            if (buflen > (size_t)PY_SSIZE_T_MAX / 2) {
-                PyMem_Free(buffer);
-                PyErr_NoMemory();
-                return NULL;
-            }
-            buflen = buflen * 2;
-
-            char *new_buffer = PyMem_Realloc(buffer, buflen);
-            if (new_buffer == NULL) {
-                PyMem_Free(buffer);
-                PyErr_NoMemory();
-                return NULL;
-            }
-            buffer = new_buffer;
-#ifndef NDEBUG
-            buffer[buflen - 1] = '#';
-#endif
-            len = strerror_r(code, buffer, buflen);
-        }
-    }
-    else {
-        buffer = small_buffer;
-    }
-
-    // strerror_r() always writes a trailing NUL byte
-    assert(memchr(buffer, 0, buflen) != NULL);
-    PyObject *result = PyUnicode_DecodeLocale(buffer, "surrogateescape");
-    if (buffer != small_buffer) {
-        PyMem_Free(buffer);
-    }
-    return result;
-
-#else
-    // strerror() implementation
-    char *message = strerror(code);
-    if (message == NULL) {
-        PyErr_SetString(PyExc_ValueError,
-                        "strerror() argument out of range");
-        return NULL;
-    }
-    return PyUnicode_DecodeLocale(message, "surrogateescape");
-#endif
+    return _Py_strerror(code);
 }
 
 
diff --git a/Python/errors.c b/Python/errors.c
index edb1557e23f63b..0645c688a9af7e 100644
--- a/Python/errors.c
+++ b/Python/errors.c
@@ -842,8 +842,7 @@ PyErr_SetFromErrnoWithFilenameObjects(PyObject *exc, 
PyObject *filenameObject, P
 
 #ifndef MS_WINDOWS
     if (i != 0) {
-        const char *s = strerror(i);
-        message = PyUnicode_DecodeLocale(s, "surrogateescape");
+        message = _Py_strerror(i);
     }
     else {
         /* Sometimes errno didn't get set */
diff --git a/Python/fileutils.c b/Python/fileutils.c
index 9deb474820b55f..3ea0f59f69ad1e 100644
--- a/Python/fileutils.c
+++ b/Python/fileutils.c
@@ -3302,3 +3302,97 @@ _Py_IsValidFD(int fd)
     return (fstat(fd, &st) == 0);
 #endif
 }
+
+
+// Call strerror_r(code) if available, or use strerror() otherwise. Decode the
+// result from the locale encoding using surrogateescape error handler.
+//
+// On success, return a Unicode string. On error, set an exception and return
+// NULL.
+PyObject*
+_Py_strerror(int code)
+/*[clinic end generated code: output=baebf09fa02a78f2 input=75a8673d97915a91]*/
+{
+    const char *errors = "surrogateescape";
+
+#ifdef _Py_HAVE_STRERROR_R
+   // Check which strerror_r() API is used
+#  if defined(__GLIBC__) && !((_POSIX_C_SOURCE >= 200112L) && 
!defined(_GNU_SOURCE))
+#    define Py_STRERROR_R_GNU
+#  elif defined(__ANDROID__) && defined(_GNU_SOURCE)
+#    define Py_STRERROR_R_GNU
+#  endif
+#endif
+
+#ifdef Py_STRERROR_R_GNU
+    // Implementation for the GNU flavor of strerror_r()
+
+    // On Linux, the longest translated strerror() message is 86 bytes
+    // (including the NUL byte).
+    char buffer[100];
+    char *message = strerror_r(code, buffer, Py_ARRAY_LENGTH(buffer));
+    // The strerror_r() GNU flavor doesn't provide a way to check if the error
+    // message was truncated or not.
+    //
+    // When the buffer is used, a trailing NUL byte is always written.
+    assert(message != buffer || memchr(buffer, 0, Py_ARRAY_LENGTH(buffer)) != 
NULL);
+    return PyUnicode_DecodeLocale(message, errors);
+
+#elif defined(_Py_HAVE_STRERROR_R)
+    // Implementation for the XSI-compliant flavor of strerror_r()
+
+    // On Linux and FreeBSD, the longest translated strerror() message is 86
+    // bytes (including the NUL byte).
+    char small_buffer[100];
+    size_t buflen = Py_ARRAY_LENGTH(small_buffer);
+    char *buffer = NULL;
+#ifndef NDEBUG
+    // Make sure that strerror_r() writes a trailing null byte
+    small_buffer[buflen - 1] = '#';
+#endif
+    int len = strerror_r(code, small_buffer, buflen);
+    if (len == ERANGE) {
+        while (len == ERANGE) {
+            if (buflen > (size_t)PY_SSIZE_T_MAX / 2) {
+                PyMem_Free(buffer);
+                PyErr_NoMemory();
+                return NULL;
+            }
+            buflen = buflen * 2;
+
+            char *new_buffer = PyMem_Realloc(buffer, buflen);
+            if (new_buffer == NULL) {
+                PyMem_Free(buffer);
+                PyErr_NoMemory();
+                return NULL;
+            }
+            buffer = new_buffer;
+#ifndef NDEBUG
+            buffer[buflen - 1] = '#';
+#endif
+            len = strerror_r(code, buffer, buflen);
+        }
+    }
+    else {
+        buffer = small_buffer;
+    }
+
+    // strerror_r() always writes a trailing NUL byte
+    assert(memchr(buffer, 0, buflen) != NULL);
+    PyObject *result = PyUnicode_DecodeLocale(buffer, errors);
+    if (buffer != small_buffer) {
+        PyMem_Free(buffer);
+    }
+    return result;
+
+#else
+    // strerror() implementation (usually not thread-safe)
+    char *message = strerror(code);
+    if (message == NULL) {
+        PyErr_SetString(PyExc_ValueError,
+                        "strerror() argument out of range");
+        return NULL;
+    }
+    return PyUnicode_DecodeLocale(message, errors);
+#endif
+}

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to