From 1154e69d3e9b8ac259bf39bf0df9de6151c7d32e Mon Sep 17 00:00:00 2001
From: Gerhard Rieger <gerhard@dest-unreach.org>
Date: Fri, 10 Jan 2025 19:41:34 +0100
Subject: [PATCH] writefull() respects total inactivity timeout

---
 CHANGES        |  4 ++++
 socat.c        |  2 ++
 sysutils.c     | 13 +++++++++++--
 sysutils.h     |  2 +-
 test.sh        |  6 +++---
 xio-proxy.c    |  6 +++---
 xio-readline.c |  2 +-
 xio-socks.c    |  2 +-
 xio-socks5.c   |  4 ++--
 xio.h          |  1 +
 xiolockfile.c  |  2 +-
 xiowrite.c     |  2 +-
 12 files changed, 31 insertions(+), 15 deletions(-)

diff --git a/CHANGES b/CHANGES
index f1efbcc..0588136 100644
--- a/CHANGES
+++ b/CHANGES
@@ -22,6 +22,9 @@ Corrections:
 
 	On partial write to not poll with sleep() but use select()/poll().
 
+	Partial write situations respect total inactivity timeout when
+	nonblocking.
+
 Building:
 	Disabling certain features during configure could break build process.
 
@@ -39,6 +42,7 @@ Testing:
 	SOCKS5 addresses are no longer experimental.
 	Tests: SOCKS5CONNECT_TCP4 SOCKS5LISTEN_TCP4
 
+
 ####################### V 1.8.0.2:
 
 Security:
diff --git a/socat.c b/socat.c
index 6ffbd21..c990f62 100644
--- a/socat.c
+++ b/socat.c
@@ -282,6 +282,8 @@ int main(int argc, const char *argv[]) {
 	    socat_opts.total_timeout.tv_usec =
 	       (rto-socat_opts.total_timeout.tv_sec) * 1000000;
 	 }
+	 xioparms.total_timeout.tv_sec  = socat_opts.total_timeout.tv_sec;
+	 xioparms.total_timeout.tv_usec = socat_opts.total_timeout.tv_usec;
 	 break;
       case 'u':  if (arg1[0][2])  { socat_opt_hint(stderr, arg1[0][1], arg1[0][2]); Exit(1); }
 	 socat_opts.lefttoright = true; break;
diff --git a/sysutils.c b/sysutils.c
index a581a53..f395e58 100644
--- a/sysutils.c
+++ b/sysutils.c
@@ -32,10 +32,15 @@ const int one = 1;
    Then we can test partial write with something like:
    socat -d4 -lu -b 262144 -u /dev/zero,readbytes=262144 -,o-nonblock |{ sleep 3; wc -c; }
 */
-ssize_t writefull(int fd, const void *buff, size_t bytes) {
+ssize_t writefull(
+	int fd,
+	const void *buff,
+	size_t bytes,
+	const struct timeval *tmo0) {
    size_t writt = 0;
    ssize_t chk;
    struct pollfd pfd;
+   struct timeval tmo = { 0 };
    int rc;
 
    while (1) {
@@ -51,7 +56,11 @@ ssize_t writefull(int fd, const void *buff, size_t bytes) {
 	    pfd.fd      = fd;
 	    pfd.events  = POLLOUT;
 	    pfd.revents = 0;
-	    rc = xiopoll(&pfd, 1, NULL);
+	    if (tmo0 != NULL) {
+	       tmo.tv_sec  = tmo0->tv_sec;
+	       tmo.tv_usec = tmo0->tv_usec;
+	    }
+	    rc = xiopoll(&pfd, 1, (tmo.tv_sec!=0 || tmo.tv_usec!=0) ? &tmo : NULL);
 	    if (rc == 0) {
 	       Notice("inactivity timeout triggered");
 	       errno = ETIMEDOUT;
diff --git a/sysutils.h b/sysutils.h
index 5e8ae10..cfdff04 100644
--- a/sysutils.h
+++ b/sysutils.h
@@ -47,7 +47,7 @@ struct xiorange {
 extern const int one;
 #endif
 
-extern ssize_t writefull(int fd, const void *buff, size_t bytes);
+extern ssize_t writefull(int fd, const void *buff, size_t bytes, const struct timeval *tmo0);
 
 #if _WITH_SOCKET
 extern socklen_t socket_init(int af, union sockaddr_union *sa);
diff --git a/test.sh b/test.sh
index 9d08236..c16a839 100755
--- a/test.sh
+++ b/test.sh
@@ -19053,7 +19053,7 @@ else
     da="test$N $(date) $RANDOM"
     case X$IPPORT in
 	XPORT)  newport $(tolower $PROTO); _PORT=$PORT ;;
-	XPROTO) echo "IPPROTO=\"$IPPROTO\""
+	XPROTO) #echo "IPPROTO=\"$IPPROTO\""
 		_PORT=$IPPROTO ;;
     esac
     CMD0="$TRACE $SOCAT $opts ${ADDR}:$_PORT,$option,$ACCEPT_TIMEOUT PIPE"
@@ -19146,7 +19146,7 @@ else
     da="test$N $(date) $RANDOM"
     case X$IPPORT in
 	XPORT)  newport $(tolower $PROTO); _PORT=$PORT ;;
-	XPROTO) echo "IPPROTO=\"$IPPROTO\""
+	XPROTO) #echo "IPPROTO=\"$IPPROTO\""
 		_PORT=$IPPROTO ;;
     esac
     CMD0="$TRACE $SOCAT $opts -u /dev/null $ADDR:localhost-6-4.dest-unreach.net:$_PORT,bind=127.0.0.1"
@@ -19211,7 +19211,7 @@ else
     da="test$N $(date) $RANDOM"
     case X$IPPORT in
 	XPORT)  newport $(tolower $PROTO); _PORT=$PORT ;;
-	XPROTO) echo "IPPROTO=\"$IPPROTO\""
+	XPROTO) #echo "IPPROTO=\"$IPPROTO\""
 		_PORT=$IPPROTO ;;
     esac
     CMD0="$TRACE $SOCAT $opts ${ADDR%%-*}-LISTEN:$_PORT,pf=ip4 PIPE"
diff --git a/xio-proxy.c b/xio-proxy.c
index 86d86e3..0a8d4e6 100644
--- a/xio-proxy.c
+++ b/xio-proxy.c
@@ -376,7 +376,7 @@ int _xioopen_proxy_connect(struct single *sfd,
    * xiosanitize(request, strlen(request), textbuff) = '\0';
    Info1("sending \"%s\"", textbuff);
    /* write errors are assumed to always be hard errors, no retry */
-   if (writefull(sfd->fd, request, strlen(request)) < 0) {
+   if (writefull(sfd->fd, request, strlen(request), NULL) < 0) {
       Msg4(level, "write(%d, %p, "F_Zu"): %s",
 	   sfd->fd, request, strlen(request), strerror(errno));
       if (Close(sfd->fd) < 0) {
@@ -406,7 +406,7 @@ int _xioopen_proxy_connect(struct single *sfd,
       *next = '\0';
       Info1("sending \"%s\\r\\n\"", header);
       *next++ = '\r';  *next++ = '\n'; *next++ = '\0';
-      if (writefull(sfd->fd, header, strlen(header)) < 0) {
+      if (writefull(sfd->fd, header, strlen(header), NULL) < 0) {
 	 Msg4(level, "write(%d, %p, "F_Zu"): %s",
 	      sfd->fd, header, strlen(header), strerror(errno));
 	 if (Close(sfd->fd) < 0) {
@@ -419,7 +419,7 @@ int _xioopen_proxy_connect(struct single *sfd,
    }
 
    Info("sending \"\\r\\n\"");
-   if (writefull(sfd->fd, "\r\n", 2) < 0) {
+   if (writefull(sfd->fd, "\r\n", 2, NULL) < 0) {
       Msg2(level, "write(%d, \"\\r\\n\", 2): %s",
 	   sfd->fd, strerror(errno));
       if (Close(sfd->fd) < 0) {
diff --git a/xio-readline.c b/xio-readline.c
index 376cd26..2807379 100644
--- a/xio-readline.c
+++ b/xio-readline.c
@@ -184,7 +184,7 @@ ssize_t xioread_readline(struct single *pipe, void *buff, size_t bufsiz) {
 	 /* we must carriage return, because readline will first print the
 	    prompt */
 	 ssize_t writt;
-	 writt = writefull(pipe->fd, "\r", 1);
+	 writt = writefull(pipe->fd, "\r", 1, NULL);
 	 if (writt < 0) {
 	    Warn2("write(%d, \"\\r\", 1): %s",
 		   pipe->fd, strerror(errno));
diff --git a/xio-socks.c b/xio-socks.c
index 089fd27..fd064dd 100644
--- a/xio-socks.c
+++ b/xio-socks.c
@@ -370,7 +370,7 @@ int _xioopen_socks4_connect(struct single *sfd,
       }
    }
 #endif /* WITH_MSGLEVEL <= E_DEBUG */
-   if (writefull(sfd->fd, sockhead, headlen) < 0) {
+   if (writefull(sfd->fd, sockhead, headlen, NULL) < 0) {
       Msg4(level, "write(%d, %p, "F_Zu"): %s",
 	   sfd->fd, sockhead, headlen, strerror(errno));
       if (Close(sfd->fd) < 0) {
diff --git a/xio-socks5.c b/xio-socks5.c
index d931961..31aa1ca 100644
--- a/xio-socks5.c
+++ b/xio-socks5.c
@@ -136,7 +136,7 @@ static int _xioopen_socks5_handshake(struct single *sfd, int level)
 	}
 #endif
 
-	if (writefull(sfd->fd, client_hello, client_hello_size) < 0) {
+	if (writefull(sfd->fd, client_hello, client_hello_size, NULL) < 0) {
 		Msg4(level, "write(%d, %p, %d): %s",
 		     sfd->fd, client_hello, client_hello_size,
 		     strerror(errno));
@@ -426,7 +426,7 @@ static int _xioopen_socks5_request(
 	}
 #endif
 
-	if (writefull(sfd->fd, req, bytes) < 0) {
+	if (writefull(sfd->fd, req, bytes, NULL) < 0) {
 		Msg4(level, "write(%d, %p, %d): %s",
 			sfd->fd, req, bytes, strerror(errno));
 		if (Close(sfd->fd) < 0) {
diff --git a/xio.h b/xio.h
index a276a56..ade2d0a 100644
--- a/xio.h
+++ b/xio.h
@@ -120,6 +120,7 @@ typedef struct xioparms {
    const char *sniffleft_name; 		/* file name with -r */
    const char *sniffright_name; 	/* file name with -R */
    size_t bufsiz;
+   struct timeval total_timeout;/* when nothing happens, die after seconds */
 } xioparms_t;
 
 /* pack the description of a lock file */
diff --git a/xiolockfile.c b/xiolockfile.c
index c6896d4..ff29c2f 100644
--- a/xiolockfile.c
+++ b/xiolockfile.c
@@ -52,7 +52,7 @@ int xiogetlock(const char *lockfile) {
 
    pid = Getpid();
    bytes = sprintf(pidbuf, F_pid"\n", pid);
-   if (writefull(fd, pidbuf, bytes) < 0) {
+   if (writefull(fd, pidbuf, bytes, NULL) < 0) {
       Error4("write(%d, %p, "F_Zu"): %s", fd, pidbuf, bytes, strerror(errno));
       return -1;
    }
diff --git a/xiowrite.c b/xiowrite.c
index a3e77ab..c5885ad 100644
--- a/xiowrite.c
+++ b/xiowrite.c
@@ -50,7 +50,7 @@ ssize_t xiowrite(xiofile_t *file, const void *buff, size_t bytes) {
    switch (pipe->dtype & XIODATA_WRITEMASK) {
 
    case XIOWRITE_STREAM:
-      writt = writefull(pipe->fd, buff, bytes);
+      writt = writefull(pipe->fd, buff, bytes, NULL);
       if (writt < 0) {
 	 _errno = errno;
 	 switch (_errno) {