[issue11650] Faulty RESTART/EINTR handling in Parser/myreadline.c

Steffen Daode Nurpmeso report at bugs.python.org
Sun Apr 10 19:41:38 CEST 2011


Steffen Daode Nurpmeso <sdaoden at googlemail.com> added the comment:

On Sat, Apr 09, 2011 at 02:18:01PM +0000, STINNER Victor wrote:
> I noticied a strange behaviour:

Still fun, but this one could even make it except for termios
flags, multibyte and the real problem, signal handling.
Hm.

----------
Added file: http://bugs.python.org/file21605/11650.termios-1.diff

_______________________________________
Python tracker <report at bugs.python.org>
<http://bugs.python.org/issue11650>
_______________________________________
-------------- next part --------------
diff --git a/Parser/myreadline.c b/Parser/myreadline.c
--- a/Parser/myreadline.c
+++ b/Parser/myreadline.c
@@ -10,6 +10,10 @@
 */
 
 #include "Python.h"
+#ifdef Py_PYPORT_H
+# define __USE_TERMIOS
+# include "signal.h"
+#endif
 #ifdef MS_WINDOWS
 #define WIN32_LEAN_AND_MEAN
 #include "windows.h"
@@ -19,6 +23,18 @@
 extern char* vms__StdioReadline(FILE *sys_stdin, FILE *sys_stdout, char *prompt);
 #endif
 
+typedef struct Args {
+    char            *input;
+    FILE            *fp;
+    int             tios_fd;
+    int             tios_is_init;
+#ifdef __USE_TERMIOS
+    int             tios_is_set;
+    int             __align;
+    struct termios  tios_old;
+    struct termios  tios_new;
+#endif
+} Args;
 
 PyThreadState* _PyOS_ReadlineTState;
 
@@ -29,117 +45,230 @@
 
 int (*PyOS_InputHook)(void) = NULL;
 
-/* This function restarts a fgets() after an EINTR error occurred
-   except if PyOS_InterruptOccurred() returns true. */
+/* This function restarts a fgetc() after an EINTR error occurred
+ * except if PyOS_InterruptOccurred() returns true */
+static int  my_fgets(Args *args);
+#ifdef __USE_TERMIOS
+static void termios_resume(Args *args);
+static void termios_suspend(Args *args);
+#endif
+
+#ifdef __USE_TERMIOS
+static void
+termios_resume(Args *args)
+{
+    if (!args->tios_is_init) {
+        args->tios_is_init = 1;
+
+        while (tcgetattr(args->tios_fd, &args->tios_old) != 0)
+            if (errno != EINTR) {
+                args->tios_fd = -1;
+                goto jleave;
+            }
+
+        memcpy(&args->tios_new, &args->tios_old, sizeof(args->tios_old));
+        args->tios_new.c_lflag &= ~(/*ECHOCTL |*/ ICANON);
+        args->tios_new.c_cc[VMIN] = 1;
+    }
+
+    if (args->tios_fd < 0)
+        goto jleave;
+
+    while (tcsetattr(args->tios_fd, TCSAFLUSH, &args->tios_new) != 0)
+        ;
+    args->tios_is_set = 1;
+
+jleave:
+    return;
+}
+
+static void
+termios_suspend(Args *args)
+{
+    if (args->tios_is_init && args->tios_is_set) {
+        while (tcsetattr(args->tios_fd, TCSANOW, &args->tios_old) != 0)
+            ;
+        args->tios_is_set = 0;
+    }
+    return;
+}
+#endif
 
 static int
-my_fgets(char *buf, int len, FILE *fp)
+my_fgets(Args *args)
 {
-    char *p;
+    int estat;
+    char *buf, *cursor;
+    size_t buf_len;
+
+    buf = (char*)PyMem_MALLOC(2*80);
+    estat = 1;
+    if (buf == NULL)
+        goto jreturn;
+
+    cursor = buf;
+    buf_len = 2*80 - 2;
+jrestart_input:
+    estat = 0;
+
+    if (PyOS_InputHook != NULL)
+        (void)(PyOS_InputHook)();
+#ifdef __USE_TERMIOS
+    termios_resume(args);
+#endif
+
+    /* Fetch bytes until error or newline */
+    errno = 0;
     while (1) {
-        if (PyOS_InputHook != NULL)
-            (void)(PyOS_InputHook)();
-        errno = 0;
-        p = fgets(buf, len, fp);
-        if (p != NULL)
-            return 0; /* No error */
+        int c = fgetc(args->fp);
+#ifdef __USE_TERMIOS
+        if (!isprint(c))
+            switch (c) {
+            case '\x04':
+                c = EOF;
+                /* FALLTHROUGH */
+            default:
+                break;
+            case '\x03':
+                estat = SIGINT;
+                goto j_sigit;
+            case '\x1A':
+                estat = SIGTSTP;
+                goto j_sigit;
+            case '\x1C':
+                estat = SIGQUIT;
+                /* FALLTHROUGH */
+j_sigit:        termios_suspend(args);
+                kill(getpid(), estat);
+                errno = EINTR;
+                goto jcheck_fail;
+            }
+#endif
+        if (c == EOF)
+            goto jcheck_fail;
+        *(cursor++) = (char)c;
+        if (c == '\n')
+            break;
+
+        if ((size_t)(cursor-buf) >= buf_len) {
+            buf_len += 2+32;
+            cursor = buf = (char*)PyMem_REALLOC(buf, buf_len);
+            if (buf == NULL) {
+                estat = 1;
+                goto jreturn;
+            }
+            buf_len -= 2+32;
+            cursor += buf_len;
+            buf_len += 32;
+        }
+    }
+
+    *cursor = '\0';
+    args->input = buf;
+jreturn:
+#ifdef __USE_TERMIOS
+    termios_suspend(args);
+#endif
+    return estat;
+
+jcheck_fail:
 #ifdef MS_WINDOWS
-        /* In the case of a Ctrl+C or some other external event
-           interrupting the operation:
-           Win2k/NT: ERROR_OPERATION_ABORTED is the most recent Win32
-           error code (and feof() returns TRUE).
-           Win9x: Ctrl+C seems to have no effect on fgets() returning
-           early - the signal handler is called, but the fgets()
-           only returns "normally" (ie, when Enter hit or feof())
+    /* In the case of a Ctrl+C or some other external event
+       interrupting the operation:
+       Win2k/NT: ERROR_OPERATION_ABORTED is the most recent Win32
+       error code (and feof() returns TRUE).
+       Win9x: Ctrl+C seems to have no effect on fgets() returning
+       early - the signal handler is called, but the fgets()
+       only returns "normally" (ie, when Enter hit or feof())
+    */
+    if (GetLastError()==ERROR_OPERATION_ABORTED) {
+        /* Signals come asynchronously, so we sleep a brief
+           moment before checking if the handler has been
+           triggered (we cant just return 1 before the
+           signal handler has been called, as the later
+           signal may be treated as a separate interrupt).
         */
-        if (GetLastError()==ERROR_OPERATION_ABORTED) {
-            /* Signals come asynchronously, so we sleep a brief
-               moment before checking if the handler has been
-               triggered (we cant just return 1 before the
-               signal handler has been called, as the later
-               signal may be treated as a separate interrupt).
-            */
-            Sleep(1);
-            if (PyOS_InterruptOccurred()) {
-                return 1; /* Interrupt */
-            }
-            /* Either the sleep wasn't long enough (need a
-               short loop retrying?) or not interrupted at all
-               (in which case we should revisit the whole thing!)
-               Logging some warning would be nice.  assert is not
-               viable as under the debugger, the various dialogs
-               mean the condition is not true.
-            */
+        Sleep(1);
+        if (PyOS_InterruptOccurred()) {
+            estat = 1;
+            goto jfail;
         }
+        /* Either the sleep wasn't long enough (need a
+           short loop retrying?) or not interrupted at all
+           (in which case we should revisit the whole thing!)
+           Logging some warning would be nice.  assert is not
+           viable as under the debugger, the various dialogs
+           mean the condition is not true.
+        */
+    }
 #endif /* MS_WINDOWS */
-        if (feof(fp)) {
-            return -1; /* EOF */
+
+    if (feof(args->fp)) {
+        estat = -1; /* EOF */
+        goto jfail;
+    }
+#ifdef EINTR
+    if (errno == EINTR) {
+# ifdef WITH_THREAD
+        PyEval_RestoreThread(_PyOS_ReadlineTState);
+# endif
+        estat = PyErr_CheckSignals();
+# ifdef WITH_THREAD
+        PyEval_SaveThread();
+# endif
+        if (estat < 0) {
+            estat = 1;
+            goto jfail;
         }
-#ifdef EINTR
-        if (errno == EINTR) {
-            int s;
-#ifdef WITH_THREAD
-            PyEval_RestoreThread(_PyOS_ReadlineTState);
+        /* EINTR is restarted */
+        goto jrestart_input;
+    }
 #endif
-            s = PyErr_CheckSignals();
-#ifdef WITH_THREAD
-            PyEval_SaveThread();
-#endif
-            if (s < 0)
-                    return 1;
-	    /* try again */
-            continue;
-        }
-#endif
-        if (PyOS_InterruptOccurred()) {
-            return 1; /* Interrupt */
-        }
-        return -2; /* Error */
+
+    if (PyOS_InterruptOccurred()) {
+        estat = 1; /* Interrupt */
+        goto jfail;
     }
-    /* NOTREACHED */
+    estat = -2; /* Error */
+    /* FALLTHROUGH */
+
+jfail:
+    PyMem_Free(buf);
+    goto jreturn;
 }
 
-
-/* Readline implementation using fgets() */
-
+/* Readline implementation using my_fgets() */
 char *
 PyOS_StdioReadline(FILE *sys_stdin, FILE *sys_stdout, char *prompt)
 {
-    size_t n;
-    char *p;
-    n = 100;
-    if ((p = (char *)PyMem_MALLOC(n)) == NULL)
-        return NULL;
+    char *result;
+    auto Args args;
+
     fflush(sys_stdout);
     if (prompt)
         fprintf(stderr, "%s", prompt);
     fflush(stderr);
-    switch (my_fgets(p, (int)n, sys_stdin)) {
+
+    args.input = NULL;
+    args.fp = sys_stdin;
+    args.tios_fd = fileno(sys_stdin);
+    args.tios_is_init = 0;
+
+    switch (my_fgets(&args)) {
     case 0: /* Normal case */
+    case 1: /* Interrupt */
+        result = args.input;
         break;
-    case 1: /* Interrupt */
-        PyMem_FREE(p);
-        return NULL;
     case -1: /* EOF */
     case -2: /* Error */
     default: /* Shouldn't happen */
-        *p = '\0';
+        result = (char*)PyMem_MALLOC(sizeof(void*));
+        if (result != NULL)
+            *result = '\0';
         break;
     }
-    n = strlen(p);
-    while (n > 0 && p[n-1] != '\n') {
-        size_t incr = n+2;
-        p = (char *)PyMem_REALLOC(p, n + incr);
-        if (p == NULL)
-            return NULL;
-        if (incr > INT_MAX) {
-            PyErr_SetString(PyExc_OverflowError, "input line too long");
-        }
-        if (my_fgets(p+n, (int)incr, sys_stdin) != 0)
-            break;
-        n += strlen(p+n);
-    }
-    return (char *)PyMem_REALLOC(p, n+1);
+
+    return result;
 }
 
 


More information about the Python-bugs-list mailing list