diff --git a/CHANGES b/CHANGES index 0cf1165..9796e57 100644 --- a/CHANGES +++ b/CHANGES @@ -45,6 +45,11 @@ Features: before exit. Signal USR1 logs actual values. Tests: OPTION_STATISTICS SIGUSR1_STATISTICS + Added option sitout-eio to specify a timerange in which EIO on the pty + of a sub process is tolerated. + Red Hat issue 1853102 related. + Thanks to Jonathan Casiot for sending an initial patch. + Corrections: When a sub process (EXEC, SYSTEM) terminated with exit code other than 0, its last sent data might have been lost depending on timing of read/ diff --git a/doc/socat.yo b/doc/socat.yo index b94b8b4..c55d170 100644 --- a/doc/socat.yo +++ b/doc/socat.yo @@ -2823,6 +2823,12 @@ label(OPTION_PTY_INTERVAL)dit(bf(tt(pty-interval=))) periodically checks the HUP condition using tt(poll()) to find if the pty's slave side has been opened. The default polling interval is 1s. Use the pty-interval option [link(timeval)(TYPE_TIMEVAL)] to change this value. +label(OPTION_SITOUT_EIO)dit(bf(tt(sitout-eio=))) + The login program in Linux closes its tty/pty and reopens it for security + reasons. During this time the pty master would get EIO on I/O operations and + might terminate. With this option socat() tolerates EIO for the specified + time. Please note that in this state socat() blocks traffic in both + directions, even when it is not related to this channel. enddit() diff --git a/sycls.c b/sycls.c index 5d137c0..eba9d01 100644 --- a/sycls.c +++ b/sycls.c @@ -547,7 +547,11 @@ ssize_t Read(int fd, void *buf, size_t count) { _errno = errno; if (!diag_in_handler) diag_flush(); #if WITH_SYCLS - Debug1("read -> "F_Zd, result); + if (result < 0) { + Debug2("read -> "F_Zd" (errno=%d)", result, _errno); + } else { + Debug1("read -> "F_Zd, result); + } #endif /* WITH_SYCLS */ errno = _errno; return result; diff --git a/xio-progcall.c b/xio-progcall.c index 50e314d..82822dc 100644 --- a/xio-progcall.c +++ b/xio-progcall.c @@ -21,6 +21,7 @@ const struct optdesc opt_openpty = { "openpty", NULL, OPT_OPENPTY, GROUP_P #if HAVE_DEV_PTMX || HAVE_DEV_PTC const struct optdesc opt_ptmx = { "ptmx", NULL, OPT_PTMX, GROUP_PTY, PH_BIGEN, TYPE_BOOL, OFUNC_SPEC }; #endif +const struct optdesc opt_sitout_eio = { "sitout-eio", NULL, OPT_SITOUT_EIO, GROUP_PTY, PH_INIT, TYPE_TIMEVAL, OFUNC_OFFSET, XIO_OFFSETOF(para.exec.sitout_eio), XIO_SIZEOF(para.exec.sitout_eio) }; #if WITH_EXEC || WITH_SYSTEM diff --git a/xio-progcall.h b/xio-progcall.h index 09083be..a2aa90b 100644 --- a/xio-progcall.h +++ b/xio-progcall.h @@ -10,6 +10,7 @@ extern const struct optdesc opt_fdout; extern const struct optdesc opt_path; extern const struct optdesc opt_pipes; extern const struct optdesc opt_pty; +extern const struct optdesc opt_sitout_eio; extern const struct optdesc opt_openpty; extern const struct optdesc opt_ptmx; extern const struct optdesc opt_stderr; diff --git a/xio.h b/xio.h index cb23f65..ace56b1 100644 --- a/xio.h +++ b/xio.h @@ -234,6 +234,7 @@ typedef struct single { struct { pid_t pid; /* child PID, with EXEC: */ int fdout; /* use fd for output if two pipes */ + struct timeval sitout_eio; } exec; #if WITH_READLINE struct { diff --git a/xioopts.c b/xioopts.c index ca1049b..03c1243 100644 --- a/xioopts.c +++ b/xioopts.c @@ -1482,6 +1482,7 @@ const struct optname optionnames[] = { #ifdef SIOCSPGRP IF_SOCKET ("siocspgrp", &opt_siocspgrp) #endif + IF_PTY ("sitout-eio", &opt_sitout_eio) IF_TUN ("slave", &opt_iff_slave) IF_SOCKET ("sndbuf", &opt_so_sndbuf) IF_SOCKET ("sndbuf-late", &opt_so_sndbuf_late) diff --git a/xioopts.h b/xioopts.h index fe0aafa..4b95a6d 100644 --- a/xioopts.h +++ b/xioopts.h @@ -644,6 +644,7 @@ enum e_optcode { #ifdef SIOCSPGRP OPT_SIOCSPGRP, #endif + OPT_SITOUT_EIO, #ifdef SO_ACCEPTCONN OPT_SO_ACCEPTCONN, #endif /* SO_ACCEPTCONN */ diff --git a/xioread.c b/xioread.c index de8c931..857d679 100644 --- a/xioread.c +++ b/xioread.c @@ -75,23 +75,48 @@ ssize_t xioread(xiofile_t *file, void *buff, size_t bufsiz) { break; case XIOREAD_PTY: - do { + { + int eio = 0; + bool m = false; /* loop message printed? */ + while (true) { + do { bytes = Read(pipe->fd, buff, bufsiz); - } while (bytes < 0 && errno == EINTR); - if (bytes < 0) { + } while (bytes < 0 && errno == EINTR); + if (bytes < 0) { _errno = errno; - if (_errno == EIO) { - Notice4("read(%d, %p, "F_Zu"): %s (probably PTY closed)", - pipe->fd, buff, bufsiz, strerror(_errno)); - return 0; - } else { - Error4("read(%d, %p, "F_Zu"): %s", - pipe->fd, buff, bufsiz, strerror(_errno)); + if (_errno != EIO) { + Error4("read(%d, %p, "F_Zu"): %s", pipe->fd, buff, bufsiz, strerror(_errno)); + errno = _errno; + return -1; } - errno = _errno; - return -1; + if (pipe->para.exec.sitout_eio.tv_sec == 0 && + pipe->para.exec.sitout_eio.tv_usec == 0) { + Notice4("read(%d, %p, "F_Zu"): %s (probably PTY closed)", + pipe->fd, buff, bufsiz, strerror(_errno)); + return 0; + } + if (!m) { + /* Starting first iteration: calc and report */ + /* Round up to 10ms */ + eio = 100*pipe->para.exec.sitout_eio.tv_sec + + (pipe->para.exec.sitout_eio.tv_usec+9999)/10000; + Notice3("xioread(fd=%d): EIO, sitting out %u.%02lus for recovery", pipe->fd, eio/100, (unsigned long)eio%100); + m = true; + } + poll(NULL, 0, 10); + if (--eio <= 0) { + /* Timeout */ + Error4("read(%d, %p, "F_Zu"): %s", pipe->fd, buff, bufsiz, strerror(_errno)); + errno = _errno; + return -1; + } + /* Not reached */ + } else + break; /* no error */ } - break; + } + return bytes; + break; #if WITH_READLINE case XIOREAD_READLINE: