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; }