Issue #23694: Handle EINTR in _Py_open() and _Py_fopen_obj()

Retry open()/fopen() if it fails with EINTR and the Python signal handler
doesn't raise an exception.
diff --git a/Python/fileutils.c b/Python/fileutils.c
index a198625..fa481ae 100644
--- a/Python/fileutils.c
+++ b/Python/fileutils.c
@@ -912,6 +912,7 @@
 _Py_open_impl(const char *pathname, int flags, int gil_held)
 {
     int fd;
+    int async_err = 0;
 #ifndef MS_WINDOWS
     int *atomic_flag_works;
 #endif
@@ -926,10 +927,14 @@
 #endif
 
     if (gil_held) {
-        Py_BEGIN_ALLOW_THREADS
-        fd = open(pathname, flags);
-        Py_END_ALLOW_THREADS
-
+        do {
+            Py_BEGIN_ALLOW_THREADS
+            fd = open(pathname, flags);
+            Py_END_ALLOW_THREADS
+        } while (fd < 0
+                 && errno == EINTR && !(async_err = PyErr_CheckSignals()));
+        if (async_err)
+            return -1;
         if (fd < 0) {
             PyErr_SetFromErrnoWithFilename(PyExc_OSError, pathname);
             return -1;
@@ -957,6 +962,9 @@
 
    The file descriptor is created non-inheritable.
 
+   When interrupted by a signal (open() fails with EINTR), retry the syscall,
+   except if the Python signal handler raises an exception.
+
    The GIL must be held. */
 int
 _Py_open(const char *pathname, int flags)
@@ -969,7 +977,9 @@
 /* Open a file with the specified flags (wrapper to open() function).
    Return a file descriptor on success. Set errno and return -1 on error.
 
-   The file descriptor is created non-inheritable. */
+   The file descriptor is created non-inheritable.
+
+   If interrupted by a signal, fail with EINTR. */
 int
 _Py_open_noraise(const char *pathname, int flags)
 {
@@ -979,7 +989,9 @@
 /* Open a file. Use _wfopen() on Windows, encode the path to the locale
    encoding and use fopen() otherwise.
 
-   The file descriptor is created non-inheritable). */
+   The file descriptor is created non-inheritable.
+
+   If interrupted by a signal, fail with EINTR. */
 FILE *
 _Py_wfopen(const wchar_t *path, const wchar_t *mode)
 {
@@ -1012,7 +1024,9 @@
 
 /* Wrapper to fopen().
 
-   The file descriptor is created non-inheritable). */
+   The file descriptor is created non-inheritable.
+
+   If interrupted by a signal, fail with EINTR. */
 FILE*
 _Py_fopen(const char *pathname, const char *mode)
 {
@@ -1034,11 +1048,15 @@
 
    The file descriptor is created non-inheritable.
 
+   When interrupted by a signal (open() fails with EINTR), retry the syscall,
+   except if the Python signal handler raises an exception.
+
    The GIL must be held. */
 FILE*
 _Py_fopen_obj(PyObject *path, const char *mode)
 {
     FILE *f;
+    int async_err = 0;
 #ifdef MS_WINDOWS
     wchar_t *wpath;
     wchar_t wmode[10];
@@ -1062,9 +1080,12 @@
         return NULL;
     }
 
-    Py_BEGIN_ALLOW_THREADS
-    f = _wfopen(wpath, wmode);
-    Py_END_ALLOW_THREADS
+    do {
+        Py_BEGIN_ALLOW_THREADS
+        f = _wfopen(wpath, wmode);
+        Py_END_ALLOW_THREADS
+    } while (f == NULL
+             && errno == EINTR && !(async_err = PyErr_CheckSignals()));
 #else
     PyObject *bytes;
     char *path_bytes;
@@ -1075,12 +1096,18 @@
         return NULL;
     path_bytes = PyBytes_AS_STRING(bytes);
 
-    Py_BEGIN_ALLOW_THREADS
-    f = fopen(path_bytes, mode);
-    Py_END_ALLOW_THREADS
+    do {
+        Py_BEGIN_ALLOW_THREADS
+        f = fopen(path_bytes, mode);
+        Py_END_ALLOW_THREADS
+    } while (f == NULL
+             && errno == EINTR && !(async_err = PyErr_CheckSignals()));
 
     Py_DECREF(bytes);
 #endif
+    if (async_err)
+        return NULL;
+
     if (f == NULL) {
         PyErr_SetFromErrnoWithFilenameObject(PyExc_OSError, path);
         return NULL;