This one is annoying. Again poll handling is throwing me a curve ball. On MacOS X POLLHUP is signaled super early and many downloads fail.
The issue here is that tls_read() and tls_write() are not like read() and write() from a poll perspective. While parsing the HTTP header for example one should call tls_read() until a TLS_WANT_* return happens. Only then a pass via poll() should happen. In the current code there are many cases where WANT_POLLIN is returned where actually another tls_read() should happen. It is similar but not as bad for tls_write(). This diff tries to cleanup the mess a bit. In http_read() WANT_POLLIN should only be triggered by tls_read(). In most cases we try to push forward through multiple states as long as possible. I solved this with two goto labels in http_read(). Because of this some of the http_nextstep() handling was pulled into http_read() and e.g. data_write() is now also directly calling into http_read() instead of passing via poll(). I tried this on OpenBSD, Linux and MacOS X and this version seems to work on all three systems. Please test since I may have missed or introduced another poll related issue. -- :wq Claudio Index: http.c =================================================================== RCS file: /cvs/src/usr.sbin/rpki-client/http.c,v retrieving revision 1.28 diff -u -p -r1.28 http.c --- http.c 13 Apr 2021 13:54:15 -0000 1.28 +++ http.c 15 Apr 2021 13:24:57 -0000 @@ -123,6 +123,8 @@ struct tls_config *tls_config; uint8_t *tls_ca_mem; size_t tls_ca_size; +static int http_write(struct http_connection *); + /* * Return a string that can be used in error message to identify the * connection. @@ -699,7 +701,7 @@ http_redirect(struct http_connection *co if (http_resolv(conn, host, port) == -1) return -1; - return http_connect(conn); + return -2; } static int @@ -846,7 +848,9 @@ http_read(struct http_connection *conn) { ssize_t s; char *buf; + int done; +read_more: s = tls_read(conn->tls, conn->buf + conn->bufpos, conn->bufsz - conn->bufpos); if (s == -1) { @@ -867,38 +871,63 @@ http_read(struct http_connection *conn) conn->bufpos += s; +again: switch (conn->state) { case STATE_RESPONSE_STATUS: buf = http_get_line(conn); if (buf == NULL) - return WANT_POLLIN; + goto read_more; if (http_parse_status(conn, buf) == -1) { free(buf); return -1; } free(buf); conn->state = STATE_RESPONSE_HEADER; - /* FALLTHROUGH */ + goto again; case STATE_RESPONSE_HEADER: - while ((buf = http_get_line(conn)) != NULL) { + done = 0; + while (!done) { int rv; + buf = http_get_line(conn); + if (buf == NULL) + goto read_more; + rv = http_parse_header(conn, buf); free(buf); - if (rv != 1) - return rv; + if (rv == -1) + return -1; + if (rv == -2) /* redirect */ + return 0; + if (rv == 0) + done = 1; } - return WANT_POLLIN; + + /* Check status header and decide what to do next */ + if (conn->status == 200) { + if (conn->chunked) + conn->state = STATE_RESPONSE_CHUNKED; + else + conn->state = STATE_RESPONSE_DATA; + goto again; + } else if (conn->status == 304) { + http_done(conn->id, HTTP_NOT_MOD, conn->last_modified); + } else { + http_done(conn->id, HTTP_FAILED, conn->last_modified); + } + + conn->state = STATE_DONE; + return 0; case STATE_RESPONSE_DATA: if (conn->bufpos == conn->bufsz || conn->iosz <= (off_t)conn->bufpos) return 0; - return WANT_POLLIN; + goto read_more; case STATE_RESPONSE_CHUNKED: while (conn->iosz == 0) { buf = http_get_line(conn); if (buf == NULL) - return WANT_POLLIN; + goto read_more; switch (http_parse_chunked(conn, buf)) { case -1: free(buf); @@ -913,7 +942,7 @@ http_read(struct http_connection *conn) if (conn->bufpos == conn->bufsz || conn->iosz <= (off_t)conn->bufpos) return 0; - return WANT_POLLIN; + goto read_more; default: errx(1, "unexpected http state"); } @@ -939,6 +968,7 @@ http_write(struct http_connection *conn) conn->bufpos += s; if (conn->bufpos == conn->bufsz) return 0; + return WANT_POLLOUT; } @@ -987,8 +1017,13 @@ data_write(struct http_connection *conn) } /* all data written, switch back to read */ - if (conn->bufpos == 0 || conn->iosz == 0) - return 0; + if (conn->bufpos == 0 || conn->iosz == 0) { + if (conn->chunked) + conn->state = STATE_RESPONSE_CHUNKED; + else + conn->state = STATE_RESPONSE_DATA; + return http_read(conn); + } /* still more data to write in buffer */ return WANT_POLLOUT; @@ -1003,25 +1038,9 @@ data_write(struct http_connection *conn) static int http_handle(struct http_connection *conn, int events) { - /* special case for INIT, there is no event to start a request */ - if (conn->state == STATE_INIT) - return http_connect(conn); - - if (events & POLLHUP) { - if (conn->state == STATE_WRITE_DATA) - warnx("%s: output pipe closed", http_info(conn->url)); - else - warnx("%s: server closed connection", - http_info(conn->url)); - return -1; - } - - if ((events & conn->events) == 0) { - warnx("%s: unexpected event", http_info(conn->url)); - return -1; - } - switch (conn->state) { + case STATE_INIT: + return http_connect(conn); case STATE_CONNECT: if (http_finish_connect(conn) == -1) /* something went wrong, try other host */ @@ -1037,10 +1056,13 @@ http_handle(struct http_connection *conn case STATE_RESPONSE_CHUNKED: return http_read(conn); case STATE_WRITE_DATA: + if (events & POLLHUP) { + warnx("%s: output closed", http_info(conn->url)); + return -1; + } return data_write(conn); case STATE_DONE: return http_close(conn); - case STATE_INIT: case STATE_FREE: errx(1, "bad http state"); } @@ -1058,7 +1080,7 @@ http_nextstep(struct http_connection *co switch (conn->state) { case STATE_INIT: - errx(1, "bad http state"); + return http_connect(conn); case STATE_CONNECT: conn->state = STATE_TLSCONNECT; r = http_tls_connect(conn); @@ -1076,38 +1098,16 @@ http_nextstep(struct http_connection *co err(1, NULL); conn->bufpos = 0; conn->bufsz = HTTP_BUF_SIZE; - return WANT_POLLIN; - case STATE_RESPONSE_STATUS: - conn->state = STATE_RESPONSE_HEADER; - return WANT_POLLIN; - case STATE_RESPONSE_HEADER: - if (conn->status == 200) { - conn->state = STATE_RESPONSE_DATA; - } else { - if (conn->status == 304) - http_done(conn->id, HTTP_NOT_MOD, - conn->last_modified); - else - http_done(conn->id, HTTP_FAILED, - conn->last_modified); - conn->state = STATE_DONE; - return http_close(conn); - } - return WANT_POLLIN; + return http_read(conn); case STATE_RESPONSE_DATA: - conn->state = STATE_WRITE_DATA; - return WANT_POLLOUT; case STATE_RESPONSE_CHUNKED: conn->state = STATE_WRITE_DATA; return WANT_POLLOUT; - case STATE_WRITE_DATA: - if (conn->chunked) - conn->state = STATE_RESPONSE_CHUNKED; - else - conn->state = STATE_RESPONSE_DATA; - return WANT_POLLIN; case STATE_DONE: return http_close(conn); + case STATE_RESPONSE_STATUS: + case STATE_RESPONSE_HEADER: + case STATE_WRITE_DATA: case STATE_FREE: errx(1, "bad http state"); }