Skip to content

Commit 67545c6

Browse files
committed
gh-158893: Add internal _Py_strerror() function
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).
1 parent e19dc47 commit 67545c6

6 files changed

Lines changed: 147 additions & 100 deletions

File tree

‎Include/internal/pycore_fileutils.h‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,9 @@ extern int _Py_GetTicksPerSecond(long *ticks_per_second);
321321
// Export for '_testcapi' shared extension
322322
PyAPI_FUNC(int) _Py_IsValidFD(int fd);
323323

324+
// Export for '_remote_debugging' shared extension
325+
PyAPI_FUNC(PyObject*) _Py_strerror(int code);
326+
324327
#ifdef __cplusplus
325328
}
326329
#endif

‎Modules/_remote_debugging/subprocess.c‎

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
******************************************************************************/
77

88
#include "_remote_debugging.h"
9+
#include "pycore_fileutils.h" // _Py_strerror()
910

1011
#ifndef MS_WINDOWS
1112
#include <unistd.h>
@@ -229,9 +230,13 @@ get_child_pids_platform(pid_t target_pid, int recursive, pid_array_t *result)
229230
if (entry == NULL) {
230231
if (errno != 0) {
231232
int err = errno;
232-
_set_debug_oserror_from_errno_with_filename(err, "/proc",
233-
"Failed to read process directory '/proc': %s",
234-
strerror(err));
233+
PyObject *message = _Py_strerror(err);
234+
if (message != NULL) {
235+
_set_debug_oserror_from_errno_with_filename(err, "/proc",
236+
"Failed to read process directory '/proc': %S",
237+
message);
238+
Py_DECREF(message);
239+
}
235240
goto done;
236241
}
237242
break;
@@ -259,9 +264,13 @@ get_child_pids_platform(pid_t target_pid, int recursive, pid_array_t *result)
259264
if (closedir(proc_dir) != 0) {
260265
int err = errno;
261266
proc_dir = NULL;
262-
_set_debug_oserror_from_errno_with_filename(err, "/proc",
263-
"Failed to close process directory '/proc': %s",
264-
strerror(err));
267+
PyObject *message = _Py_strerror(err);
268+
if (message != NULL) {
269+
_set_debug_oserror_from_errno_with_filename(err, "/proc",
270+
"Failed to close process directory '/proc': %S",
271+
message);
272+
Py_DECREF(message);
273+
}
265274
goto done;
266275
}
267276
proc_dir = NULL;

‎Modules/_remote_debugging/threads.c‎

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
******************************************************************************/
77

88
#include "_remote_debugging.h"
9+
#include "pycore_fileutils.h" // _Py_strerror()
910

1011
#ifndef MS_WINDOWS
1112
#include <unistd.h>
@@ -716,9 +717,13 @@ read_thread_ids(RemoteUnwinderObject *unwinder, _Py_RemoteDebug_ThreadsState *st
716717
int err = errno;
717718
closedir(dir);
718719
_Py_RemoteDebug_InitThreadsState(unwinder, st);
719-
_set_debug_oserror_from_errno_with_filename(err, task_path,
720-
"Failed to read process task directory '%s': %s",
721-
task_path, strerror(err));
720+
PyObject *message = _Py_strerror(err);
721+
if (message != NULL) {
722+
_set_debug_oserror_from_errno_with_filename(err, task_path,
723+
"Failed to read process task directory '%s': %S",
724+
task_path, message);
725+
Py_DECREF(message);
726+
}
722727
return -1;
723728
}
724729
break;
@@ -749,9 +754,13 @@ read_thread_ids(RemoteUnwinderObject *unwinder, _Py_RemoteDebug_ThreadsState *st
749754
if (closedir(dir) != 0) {
750755
int err = errno;
751756
_Py_RemoteDebug_InitThreadsState(unwinder, st);
752-
_set_debug_oserror_from_errno_with_filename(err, task_path,
753-
"Failed to close process task directory '%s': %s",
754-
task_path, strerror(err));
757+
PyObject *message = _Py_strerror(err);
758+
if (message != NULL) {
759+
_set_debug_oserror_from_errno_with_filename(err, task_path,
760+
"Failed to close process task directory '%s': %S",
761+
task_path, message);
762+
Py_DECREF(message);
763+
}
755764
return -1;
756765
}
757766
st->tids = unwinder->thread_tids;
@@ -816,8 +825,12 @@ _Py_RemoteDebug_StopAllThreads(RemoteUnwinderObject *unwinder, _Py_RemoteDebug_T
816825
}
817826
if (ret < 0) {
818827
detach_threads(st, seized);
819-
_set_debug_oserror_from_errno(err,
820-
"Failed to seize thread %d: %s", tid, strerror(err));
828+
PyObject *message = _Py_strerror(err);
829+
if (message != NULL) {
830+
_set_debug_oserror_from_errno(err,
831+
"Failed to seize thread %d: %S", tid, message);
832+
Py_DECREF(message);
833+
}
821834
_Py_RemoteDebug_InitThreadsState(unwinder, st);
822835
return -1;
823836
}
@@ -827,8 +840,12 @@ _Py_RemoteDebug_StopAllThreads(RemoteUnwinderObject *unwinder, _Py_RemoteDebug_T
827840
err = errno;
828841
if (err != ESRCH) {
829842
detach_threads(st, seized);
830-
_set_debug_oserror_from_errno(err,
831-
"Failed to interrupt thread %d: %s", tid, strerror(err));
843+
PyObject *message = _Py_strerror(err);
844+
if (message != NULL) {
845+
_set_debug_oserror_from_errno(err,
846+
"Failed to interrupt thread %d: %S", tid, message);
847+
Py_DECREF(message);
848+
}
832849
_Py_RemoteDebug_InitThreadsState(unwinder, st);
833850
return -1;
834851
}
@@ -839,8 +856,12 @@ _Py_RemoteDebug_StopAllThreads(RemoteUnwinderObject *unwinder, _Py_RemoteDebug_T
839856
err = errno;
840857
if (err != ECHILD && err != ESRCH) {
841858
detach_threads(st, seized);
842-
_set_debug_oserror_from_errno(err,
843-
"waitpid failed for thread %d: %s", tid, strerror(err));
859+
PyObject *message = _Py_strerror(err);
860+
if (message != NULL) {
861+
_set_debug_oserror_from_errno(err,
862+
"waitpid failed for thread %d: %S", tid, message);
863+
Py_DECREF(message);
864+
}
844865
_Py_RemoteDebug_InitThreadsState(unwinder, st);
845866
return -1;
846867
}

‎Modules/posixmodule.c‎

Lines changed: 1 addition & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -13923,86 +13923,7 @@ static PyObject *
1392313923
os_strerror_impl(PyObject *module, int code)
1392413924
/*[clinic end generated code: output=baebf09fa02a78f2 input=75a8673d97915a91]*/
1392513925
{
13926-
#ifdef _Py_HAVE_STRERROR_R
13927-
// Check which strerror_r() API is used
13928-
# if defined(__GLIBC__) && !((_POSIX_C_SOURCE >= 200112L) && !defined(_GNU_SOURCE))
13929-
# define Py_STRERROR_R_GNU
13930-
# elif defined(__ANDROID__) && defined(_GNU_SOURCE)
13931-
# define Py_STRERROR_R_GNU
13932-
# endif
13933-
#endif
13934-
13935-
#ifdef Py_STRERROR_R_GNU
13936-
// Implementation for the GNU flavor of strerror_r()
13937-
13938-
// On Linux, the longest translated strerror() message is 86 bytes
13939-
// (including the NUL byte).
13940-
char buffer[100];
13941-
char *message = strerror_r(code, buffer, Py_ARRAY_LENGTH(buffer));
13942-
// The strerror_r() GNU flavor doesn't provide a way to check if the error
13943-
// message was truncated or not.
13944-
//
13945-
// When the buffer is used, a trailing NUL byte is always written.
13946-
assert(message != buffer || memchr(buffer, 0, Py_ARRAY_LENGTH(buffer)) != NULL);
13947-
return PyUnicode_DecodeLocale(message, "surrogateescape");
13948-
13949-
#elif defined(_Py_HAVE_STRERROR_R)
13950-
// Implementation for the XSI-compliant flavor of strerror_r()
13951-
13952-
// On Linux and FreeBSD, the longest translated strerror() message is 86
13953-
// bytes (including the NUL byte).
13954-
char small_buffer[100];
13955-
size_t buflen = Py_ARRAY_LENGTH(small_buffer);
13956-
char *buffer = NULL;
13957-
#ifndef NDEBUG
13958-
// Make sure that strerror_r() writes a trailing null byte
13959-
small_buffer[buflen - 1] = '#';
13960-
#endif
13961-
int len = strerror_r(code, small_buffer, buflen);
13962-
if (len == ERANGE) {
13963-
while (len == ERANGE) {
13964-
if (buflen > (size_t)PY_SSIZE_T_MAX / 2) {
13965-
PyMem_Free(buffer);
13966-
PyErr_NoMemory();
13967-
return NULL;
13968-
}
13969-
buflen = buflen * 2;
13970-
13971-
char *new_buffer = PyMem_Realloc(buffer, buflen);
13972-
if (new_buffer == NULL) {
13973-
PyMem_Free(buffer);
13974-
PyErr_NoMemory();
13975-
return NULL;
13976-
}
13977-
buffer = new_buffer;
13978-
#ifndef NDEBUG
13979-
buffer[buflen - 1] = '#';
13980-
#endif
13981-
len = strerror_r(code, buffer, buflen);
13982-
}
13983-
}
13984-
else {
13985-
buffer = small_buffer;
13986-
}
13987-
13988-
// strerror_r() always writes a trailing NUL byte
13989-
assert(memchr(buffer, 0, buflen) != NULL);
13990-
PyObject *result = PyUnicode_DecodeLocale(buffer, "surrogateescape");
13991-
if (buffer != small_buffer) {
13992-
PyMem_Free(buffer);
13993-
}
13994-
return result;
13995-
13996-
#else
13997-
// strerror() implementation
13998-
char *message = strerror(code);
13999-
if (message == NULL) {
14000-
PyErr_SetString(PyExc_ValueError,
14001-
"strerror() argument out of range");
14002-
return NULL;
14003-
}
14004-
return PyUnicode_DecodeLocale(message, "surrogateescape");
14005-
#endif
13926+
return _Py_strerror(code);
1400613927
}
1400713928

1400813929

‎Python/errors.c‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -842,8 +842,7 @@ PyErr_SetFromErrnoWithFilenameObjects(PyObject *exc, PyObject *filenameObject, P
842842

843843
#ifndef MS_WINDOWS
844844
if (i != 0) {
845-
const char *s = strerror(i);
846-
message = PyUnicode_DecodeLocale(s, "surrogateescape");
845+
message = _Py_strerror(i);
847846
}
848847
else {
849848
/* Sometimes errno didn't get set */

‎Python/fileutils.c‎

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3302,3 +3302,97 @@ _Py_IsValidFD(int fd)
33023302
return (fstat(fd, &st) == 0);
33033303
#endif
33043304
}
3305+
3306+
3307+
// Call strerror_r(code) if available, or use strerror() otherwise. Decode the
3308+
// result from the locale encoding using surrogateescape error handler.
3309+
//
3310+
// On success, return a Unicode string. On error, set an exception and return
3311+
// NULL.
3312+
PyObject*
3313+
_Py_strerror(int code)
3314+
/*[clinic end generated code: output=baebf09fa02a78f2 input=75a8673d97915a91]*/
3315+
{
3316+
const char *errors = "surrogateescape";
3317+
3318+
#ifdef _Py_HAVE_STRERROR_R
3319+
// Check which strerror_r() API is used
3320+
# if defined(__GLIBC__) && !((_POSIX_C_SOURCE >= 200112L) && !defined(_GNU_SOURCE))
3321+
# define Py_STRERROR_R_GNU
3322+
# elif defined(__ANDROID__) && defined(_GNU_SOURCE)
3323+
# define Py_STRERROR_R_GNU
3324+
# endif
3325+
#endif
3326+
3327+
#ifdef Py_STRERROR_R_GNU
3328+
// Implementation for the GNU flavor of strerror_r()
3329+
3330+
// On Linux, the longest translated strerror() message is 86 bytes
3331+
// (including the NUL byte).
3332+
char buffer[100];
3333+
char *message = strerror_r(code, buffer, Py_ARRAY_LENGTH(buffer));
3334+
// The strerror_r() GNU flavor doesn't provide a way to check if the error
3335+
// message was truncated or not.
3336+
//
3337+
// When the buffer is used, a trailing NUL byte is always written.
3338+
assert(message != buffer || memchr(buffer, 0, Py_ARRAY_LENGTH(buffer)) != NULL);
3339+
return PyUnicode_DecodeLocale(message, errors);
3340+
3341+
#elif defined(_Py_HAVE_STRERROR_R)
3342+
// Implementation for the XSI-compliant flavor of strerror_r()
3343+
3344+
// On Linux and FreeBSD, the longest translated strerror() message is 86
3345+
// bytes (including the NUL byte).
3346+
char small_buffer[100];
3347+
size_t buflen = Py_ARRAY_LENGTH(small_buffer);
3348+
char *buffer = NULL;
3349+
#ifndef NDEBUG
3350+
// Make sure that strerror_r() writes a trailing null byte
3351+
small_buffer[buflen - 1] = '#';
3352+
#endif
3353+
int len = strerror_r(code, small_buffer, buflen);
3354+
if (len == ERANGE) {
3355+
while (len == ERANGE) {
3356+
if (buflen > (size_t)PY_SSIZE_T_MAX / 2) {
3357+
PyMem_Free(buffer);
3358+
PyErr_NoMemory();
3359+
return NULL;
3360+
}
3361+
buflen = buflen * 2;
3362+
3363+
char *new_buffer = PyMem_Realloc(buffer, buflen);
3364+
if (new_buffer == NULL) {
3365+
PyMem_Free(buffer);
3366+
PyErr_NoMemory();
3367+
return NULL;
3368+
}
3369+
buffer = new_buffer;
3370+
#ifndef NDEBUG
3371+
buffer[buflen - 1] = '#';
3372+
#endif
3373+
len = strerror_r(code, buffer, buflen);
3374+
}
3375+
}
3376+
else {
3377+
buffer = small_buffer;
3378+
}
3379+
3380+
// strerror_r() always writes a trailing NUL byte
3381+
assert(memchr(buffer, 0, buflen) != NULL);
3382+
PyObject *result = PyUnicode_DecodeLocale(buffer, errors);
3383+
if (buffer != small_buffer) {
3384+
PyMem_Free(buffer);
3385+
}
3386+
return result;
3387+
3388+
#else
3389+
// strerror() implementation (usually not thread-safe)
3390+
char *message = strerror(code);
3391+
if (message == NULL) {
3392+
PyErr_SetString(PyExc_ValueError,
3393+
"strerror() argument out of range");
3394+
return NULL;
3395+
}
3396+
return PyUnicode_DecodeLocale(message, errors);
3397+
#endif
3398+
}

0 commit comments

Comments
 (0)