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]