Hi.
> Now soliciting diffs to change readwrite to a loop with two buffers
> that poll()s in all four directions. :)
Good thing you made me remember I wrote just this a while ago.
This is my first OpenBSD diff, so tell me if I missed anything obvious.
Tested quite extensively originally; for this diff I only checked a
simple nc to nc "hello".
Index: netcat.c
===================================================================
RCS file: /mount/cvsdev/cvs/openbsd/src/usr.bin/nc/netcat.c,v
retrieving revision 1.121
diff -u -p -r1.121 netcat.c
--- netcat.c 10 Jun 2014 16:35:42 -0000 1.121
+++ netcat.c 26 Jun 2014 11:29:45 -0000
@@ -65,6 +65,12 @@
#define PORT_MAX_LEN 6
#define UNIX_DG_TMP_SOCKET_SIZE 19
+#define POLL_STDIN 0
+#define POLL_NETOUT 1
+#define POLL_NETIN 2
+#define POLL_STDOUT 3
+#define BUFSIZE 16384
+
/* Command Line Options */
int dflag; /* detached, no stdin */
int Fflag; /* fdpass sock to stdout */
@@ -112,6 +118,8 @@ void set_common_sockopts(int);
int map_tos(char *, int *);
void report_connect(const struct sockaddr *, socklen_t);
void usage(int);
+ssize_t drainbuf(int, unsigned char *, size_t *);
+ssize_t fillbuf(int, unsigned char *, size_t *);
int
main(int argc, char *argv[])
@@ -608,7 +616,7 @@ remote_connect(const char *host, const c
if (bind(s, (struct sockaddr *)ares->ai_addr,
ares->ai_addrlen) < 0)
- err(1, "bind failed");
+ errx(1, "bind failed: %s", strerror(errno));
freeaddrinfo(ares);
}
@@ -640,7 +648,7 @@ timeout_connect(int s, const struct sock
if (timeout != -1) {
flags = fcntl(s, F_GETFL, 0);
if (fcntl(s, F_SETFL, flags | O_NONBLOCK) == -1)
- err(1, "set non-blocking mode");
+ warn("unable to set non-blocking mode");
}
if ((ret = connect(s, name, namelen)) != 0 && errno == EINPROGRESS) {
@@ -730,67 +738,229 @@ local_listen(char *host, char *port, str
* Loop that polls on the network file descriptor and stdin.
*/
void
-readwrite(int nfd)
+readwrite(int net_fd)
{
- struct pollfd pfd[2];
- unsigned char buf[16 * 1024];
- int n, wfd = fileno(stdin);
- int lfd = fileno(stdout);
- int plen;
-
- plen = sizeof(buf);
-
- /* Setup Network FD */
- pfd[0].fd = nfd;
- pfd[0].events = POLLIN;
+ struct pollfd pfd[4];
+ int stdin_fd = STDIN_FILENO;
+ int stdout_fd = STDOUT_FILENO;
+ unsigned char netinbuf[BUFSIZE];
+ size_t netinbufpos = 0;
+ unsigned char stdinbuf[BUFSIZE];
+ size_t stdinbufpos = 0;
+ int n, num_fds, flags;
+ ssize_t ret;
+
+ /* don't read from stdin if requested */
+ if (dflag)
+ stdin_fd = -1;
+
+ /* stdin */
+ pfd[POLL_STDIN].fd = stdin_fd;
+ pfd[POLL_STDIN].events = POLLIN;
+
+ /* network out */
+ pfd[POLL_NETOUT].fd = net_fd;
+ pfd[POLL_NETOUT].events = 0;
+
+ /* network in */
+ pfd[POLL_NETIN].fd = net_fd;
+ pfd[POLL_NETIN].events = POLLIN;
+
+ /* stdout */
+ pfd[POLL_STDOUT].fd = stdout_fd;
+ pfd[POLL_STDOUT].events = 0;
+
+
+ /* make all fds non-blocking */
+ for (n = 0; n < 4; n++) {
+ if (pfd[n].fd == -1)
+ continue;
+ flags = fcntl(pfd[n].fd, F_GETFL, 0);
+ /*
+ * For sockets and pipes, we want non-block, but setting it
+ * might fail for files or devices, so we ignore the return
+ * code.
+ */
+ fcntl(pfd[n].fd, F_SETFL, flags | O_NONBLOCK);
+ }
- /* Set up STDIN FD. */
- pfd[1].fd = wfd;
- pfd[1].events = POLLIN;
+ while (1) {
+ /* both inputs are gone, buffers are empty, we are done */
+ if (pfd[POLL_STDIN].fd == -1 && pfd[POLL_NETIN].fd == -1
+ && stdinbufpos == 0 && netinbufpos == 0) {
+ close(net_fd);
+ return;
+ }
+ /* both outputs are gone, we can't continue */
+ if (pfd[POLL_NETOUT].fd == -1 && pfd[POLL_STDOUT].fd == -1) {
+ close(net_fd);
+ return;
+ }
+ /* listen and net in gone, queues empty, done */
+ if (lflag && pfd[POLL_NETIN].fd == -1
+ && stdinbufpos == 0 && netinbufpos == 0) {
+ close(net_fd);
+ return;
+ }
- while (pfd[0].fd != -1) {
+ /* help says -i is for "wait between lines sent". We read and
+ * write arbitray amounts of data, and we don't want to start
+ * scanning for newlines, so this is as good as it gets */
if (iflag)
sleep(iflag);
- if ((n = poll(pfd, 2 - dflag, timeout)) < 0) {
- close(nfd);
- err(1, "Polling Error");
+ /* poll */
+ num_fds = poll(pfd, 4, timeout);
+
+ /* treat poll errors */
+ if (num_fds == -1) {
+ close(net_fd);
+ err(1, "polling error");
}
- if (n == 0)
+ /* timeout happened */
+ if (num_fds == 0)
return;
- if (pfd[0].revents & POLLIN) {
- if ((n = read(nfd, buf, plen)) < 0)
- return;
- else if (n == 0) {
- shutdown(nfd, SHUT_RD);
- pfd[0].fd = -1;
- pfd[0].events = 0;
- } else {
- if (tflag)
- atelnet(nfd, buf, n);
- if (atomicio(vwrite, lfd, buf, n) != n)
- return;
+ /* treat socket error conditions */
+ for (n = 0; n < 4; n++) {
+ if (pfd[n].revents & (POLLERR|POLLNVAL)) {
+ pfd[n].fd = -1;
+ continue;
}
}
+ /* only treat the fds used for writing, because
+ * reading is always possible even after HUP */
+ if (pfd[POLL_NETOUT].revents & POLLHUP) {
+ if (Nflag)
+ shutdown(pfd[POLL_NETOUT].fd, SHUT_WR);
+ pfd[POLL_NETOUT].fd = -1;
+ }
+ /* if HUP, stop watching stdout */
+ if (pfd[POLL_STDOUT].revents & POLLHUP)
+ pfd[POLL_STDOUT].fd = -1;
+ /* if no net out, stop watching stdin */
+ if (pfd[POLL_NETOUT].fd == -1)
+ pfd[POLL_STDIN].fd = -1;
+ /* if no stdout, stop watching net in */
+ if (pfd[POLL_STDOUT].fd == -1) {
+ if (pfd[POLL_NETIN].fd != -1)
+ shutdown(pfd[POLL_NETIN].fd, SHUT_RD);
+ pfd[POLL_NETIN].fd = -1;
+ }
- if (!dflag && pfd[1].revents & POLLIN) {
- if ((n = read(wfd, buf, plen)) < 0)
- return;
- else if (n == 0) {
- if (Nflag)
- shutdown(nfd, SHUT_WR);
- pfd[1].fd = -1;
- pfd[1].events = 0;
- } else {
- if (atomicio(vwrite, nfd, buf, n) != n)
- return;
+ /* try to read from stdin */
+ if (pfd[POLL_STDIN].revents & POLLIN && stdinbufpos < BUFSIZE) {
+ ret = fillbuf(pfd[POLL_STDIN].fd, stdinbuf,
+ &stdinbufpos);
+ /* error or eof on stdin - remove from pfd */
+ if (ret == 0 || ret == -1)
+ pfd[POLL_STDIN].fd = -1;
+ /* read something - poll net out */
+ if (stdinbufpos > 0)
+ pfd[POLL_NETOUT].events = POLLOUT;
+ /* filled buffer - remove self from polling */
+ if (stdinbufpos == BUFSIZE)
+ pfd[POLL_STDIN].events = 0;
+ }
+ /* try to write to network */
+ if (pfd[POLL_NETOUT].revents & POLLOUT && stdinbufpos > 0) {
+ ret = drainbuf(pfd[POLL_NETOUT].fd, stdinbuf,
+ &stdinbufpos);
+ if (ret == -1)
+ pfd[POLL_NETOUT].fd = -1;
+ /* buffer empty - remove self from polling */
+ if (stdinbufpos == 0)
+ pfd[POLL_NETOUT].events = 0;
+ /* buffer no longer full - poll stdin again */
+ if (stdinbufpos < BUFSIZE)
+ pfd[POLL_STDIN].events = POLLIN;
+ }
+ /* try to read from network */
+ if (pfd[POLL_NETIN].revents & POLLIN && netinbufpos < BUFSIZE) {
+ ret = fillbuf(pfd[POLL_NETIN].fd, netinbuf,
+ &netinbufpos);
+ if (ret == -1)
+ pfd[POLL_NETIN].fd = -1;
+ /* eof on net in - remove from pfd */
+ if (ret == 0) {
+ shutdown(pfd[POLL_NETIN].fd, SHUT_RD);
+ pfd[POLL_NETIN].fd = -1;
}
+ /* read something - poll stdout */
+ if (netinbufpos > 0)
+ pfd[POLL_STDOUT].events = POLLOUT;
+ /* filled buffer - remove self from polling */
+ if (netinbufpos == BUFSIZE)
+ pfd[POLL_NETIN].events = 0;
+ /* handle telnet */
+ if (tflag)
+ atelnet(pfd[POLL_NETIN].fd, netinbuf,
+ netinbufpos);
+ }
+ /* try to write to stdout */
+ if (pfd[POLL_STDOUT].revents & POLLOUT && netinbufpos > 0) {
+ ret = drainbuf(pfd[POLL_STDOUT].fd, netinbuf,
+ &netinbufpos);
+ if (ret == -1)
+ pfd[POLL_STDOUT].fd = -1;
+ /* buffer empty - remove self from polling */
+ if (netinbufpos == 0)
+ pfd[POLL_STDOUT].events = 0;
+ /* buffer no longer full - poll net in again */
+ if (netinbufpos < BUFSIZE)
+ pfd[POLL_NETIN].events = POLLIN;
+ }
+
+ /* stdin gone and queue empty? */
+ if (pfd[POLL_STDIN].fd == -1 && stdinbufpos == 0) {
+ if (pfd[POLL_NETOUT].fd != -1 && Nflag)
+ shutdown(pfd[POLL_NETOUT].fd, SHUT_WR);
+ pfd[POLL_NETOUT].fd = -1;
}
+ /* net in gone and queue empty? */
+ if (pfd[POLL_NETIN].fd == -1 && netinbufpos == 0)
+ pfd[POLL_STDOUT].fd = -1;
}
}
+ssize_t
+drainbuf(int fd, unsigned char *buf, size_t *bufpos)
+{
+ ssize_t n;
+ ssize_t adjust;
+
+ n = write(fd, buf, *bufpos);
+ /* don't treat EAGAIN, EINTR as error */
+ if (n == -1 && (errno == EAGAIN || errno == EINTR))
+ n = -2;
+ if (n <= 0)
+ return n;
+ /* adjust buffer */
+ adjust = *bufpos - n;
+ if (adjust > 0)
+ memmove(buf, buf + n, adjust);
+ *bufpos -= n;
+ return n;
+}
+
+
+ssize_t
+fillbuf(int fd, unsigned char *buf, size_t *bufpos)
+{
+ size_t num = BUFSIZE - *bufpos;
+ ssize_t n;
+
+ n = read(fd, buf + *bufpos, num);
+ /* don't treat EAGAIN, EINTR as error */
+ if (n == -1 && (errno == EAGAIN || errno == EINTR))
+ n = -2;
+ if (n <= 0)
+ return n;
+ *bufpos += n;
+ return n;
+}
+
/*
* fdpass()
* Pass the connected file descriptor to stdout and exit.
@@ -857,6 +1027,8 @@ atelnet(int nfd, unsigned char *buf, uns
{
unsigned char *p, *end;
unsigned char obuf[4];
+ int flags;
+ int blocking = 0;
if (size < 3)
return;
@@ -877,8 +1049,20 @@ atelnet(int nfd, unsigned char *buf, uns
p++;
obuf[2] = *p;
+
+ if (!blocking) {
+ flags = fcntl(nfd, F_GETFL, 0);
+ if (fcntl(nfd, F_SETFL, flags & ~O_NONBLOCK) == -1)
+ warn("unable to set blocking mode");
+ blocking = 1;
+ }
if (atomicio(vwrite, nfd, obuf, 3) != 3)
warn("Write Error!");
+ }
+ if (blocking) {
+ flags = fcntl(nfd, F_GETFL, 0);
+ if (fcntl(nfd, F_SETFL, flags | O_NONBLOCK) == -1)
+ warn("unable to set non-blocking mode");
}
}