aboutsummaryrefslogtreecommitdiffstats
path: root/package/uhttpd/src/uhttpd-utils.c
diff options
context:
space:
mode:
Diffstat (limited to 'package/uhttpd/src/uhttpd-utils.c')
-rw-r--r--package/uhttpd/src/uhttpd-utils.c250
1 files changed, 164 insertions, 86 deletions
diff --git a/package/uhttpd/src/uhttpd-utils.c b/package/uhttpd/src/uhttpd-utils.c
index 18969e74dd..dec952357e 100644
--- a/package/uhttpd/src/uhttpd-utils.c
+++ b/package/uhttpd/src/uhttpd-utils.c
@@ -1,7 +1,7 @@
/*
* uhttpd - Tiny single-threaded httpd - Utility functions
*
- * Copyright (C) 2010 Jo-Philipp Wich <xm@subsignal.org>
+ * Copyright (C) 2010-2012 Jo-Philipp Wich <xm@subsignal.org>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -103,120 +103,171 @@ char *strfind(char *haystack, int hslen, const char *needle, int ndlen)
return NULL;
}
-/* interruptable select() */
-int select_intr(int n, fd_set *r, fd_set *w, fd_set *e, struct timeval *t)
+bool uh_socket_wait(int fd, int sec, bool write)
{
int rv;
- sigset_t ssn, sso;
+ struct timeval timeout;
- /* unblock SIGCHLD */
- sigemptyset(&ssn);
- sigaddset(&ssn, SIGCHLD);
- sigaddset(&ssn, SIGPIPE);
- sigprocmask(SIG_UNBLOCK, &ssn, &sso);
+ fd_set fds;
- rv = select(n, r, w, e, t);
+ FD_ZERO(&fds);
+ FD_SET(fd, &fds);
- /* restore signal mask */
- sigprocmask(SIG_SETMASK, &sso, NULL);
+ timeout.tv_sec = sec;
+ timeout.tv_usec = 0;
- return rv;
-}
+ while (((rv = select(fd+1, write ? NULL : &fds, write ? &fds : NULL,
+ NULL, &timeout)) < 0) && (errno == EINTR))
+ {
+ D("IO: Socket(%d) select interrupted: %s\n",
+ fd, strerror(errno));
+ continue;
+ }
-int uh_tcp_send_lowlevel(struct client *cl, const char *buf, int len)
-{
- fd_set writer;
- struct timeval timeout;
+ if (rv <= 0)
+ {
+ D("IO: Socket(%d) appears dead (rv=%d)\n", fd, rv);
+ return false;
+ }
+
+ return true;
+}
- FD_ZERO(&writer);
- FD_SET(cl->socket, &writer);
+static int __uh_raw_send(struct client *cl, const char *buf, int len, int sec,
+ int (*wfn) (struct client *, const char *, int))
+{
+ ssize_t rv;
+ int fd = cl->fd.fd;
- timeout.tv_sec = cl->server->conf->network_timeout;
- timeout.tv_usec = 0;
+ while (true)
+ {
+ if ((rv = wfn(cl, buf, len)) < 0)
+ {
+ if (errno == EINTR)
+ {
+ D("IO: Socket(%d) interrupted\n", cl->fd.fd);
+ continue;
+ }
+ else if ((sec > 0) && (errno == EAGAIN || errno == EWOULDBLOCK))
+ {
+ if (!uh_socket_wait(fd, sec, true))
+ return -1;
+ }
+ else
+ {
+ D("IO: Socket(%d) write error: %s\n", fd, strerror(errno));
+ return -1;
+ }
+ }
+ /*
+ * It is not entirely clear whether rv = 0 on nonblocking sockets
+ * is an error. In real world fuzzing tests, not handling it as close
+ * led to tight infinite loops in this send procedure, so treat it as
+ * closed and break out.
+ */
+ else if (rv == 0)
+ {
+ D("IO: Socket(%d) closed\n", fd);
+ return 0;
+ }
+ else if (rv < len)
+ {
+ D("IO: Socket(%d) short write %d/%d bytes\n", fd, rv, len);
+ len -= rv;
+ buf += rv;
+ continue;
+ }
+ else
+ {
+ D("IO: Socket(%d) sent %d/%d bytes\n", fd, rv, len);
+ return rv;
+ }
+ }
+}
- if (select(cl->socket + 1, NULL, &writer, NULL, &timeout) > 0)
- return send(cl->socket, buf, len, 0);
+int uh_tcp_send_lowlevel(struct client *cl, const char *buf, int len)
+{
+ return write(cl->fd.fd, buf, len);
+}
- return -1;
+int uh_raw_send(int fd, const char *buf, int len, int sec)
+{
+ struct client_light cl = { .fd = { .fd = fd } };
+ return __uh_raw_send((struct client *)&cl, buf, len, sec,
+ uh_tcp_send_lowlevel);
}
int uh_tcp_send(struct client *cl, const char *buf, int len)
{
+ int seconds = cl->server->conf->network_timeout;
#ifdef HAVE_TLS
if (cl->tls)
- return cl->server->conf->tls_send(cl, (void *)buf, len);
- else
+ return __uh_raw_send(cl, buf, len, seconds,
+ cl->server->conf->tls_send);
#endif
- return uh_tcp_send_lowlevel(cl, buf, len);
+ return __uh_raw_send(cl, buf, len, seconds, uh_tcp_send_lowlevel);
}
-int uh_tcp_peek(struct client *cl, char *buf, int len)
+static int __uh_raw_recv(struct client *cl, char *buf, int len, int sec,
+ int (*rfn) (struct client *, char *, int))
{
- /* sanity check, prevent overflowing peek buffer */
- if (len > sizeof(cl->peekbuf))
- return -1;
-
- int sz = uh_tcp_recv(cl, buf, len);
+ ssize_t rv;
+ int fd = cl->fd.fd;
- /* store received data in peek buffer */
- if (sz > 0)
+ while (true)
{
- cl->peeklen = sz;
- memcpy(cl->peekbuf, buf, sz);
+ if ((rv = rfn(cl, buf, len)) < 0)
+ {
+ if (errno == EINTR)
+ {
+ continue;
+ }
+ else if ((sec > 0) && (errno == EAGAIN || errno == EWOULDBLOCK))
+ {
+ if (!uh_socket_wait(fd, sec, false))
+ return -1;
+ }
+ else
+ {
+ D("IO: Socket(%d) read error: %s\n", fd, strerror(errno));
+ return -1;
+ }
+ }
+ else if (rv == 0)
+ {
+ D("IO: Socket(%d) closed\n", fd);
+ return 0;
+ }
+ else
+ {
+ D("IO: Socket(%d) read %d bytes\n", fd, rv);
+ return rv;
+ }
}
-
- return sz;
}
int uh_tcp_recv_lowlevel(struct client *cl, char *buf, int len)
{
- fd_set reader;
- struct timeval timeout;
-
- FD_ZERO(&reader);
- FD_SET(cl->socket, &reader);
-
- timeout.tv_sec = cl->server->conf->network_timeout;
- timeout.tv_usec = 0;
-
- if (select(cl->socket + 1, &reader, NULL, NULL, &timeout) > 0)
- return recv(cl->socket, buf, len, 0);
+ return read(cl->fd.fd, buf, len);
+}
- return -1;
+int uh_raw_recv(int fd, char *buf, int len, int sec)
+{
+ struct client_light cl = { .fd = { .fd = fd } };
+ return __uh_raw_recv((struct client *)&cl, buf, len, sec,
+ uh_tcp_recv_lowlevel);
}
int uh_tcp_recv(struct client *cl, char *buf, int len)
{
- int sz = 0;
- int rsz = 0;
-
- /* first serve data from peek buffer */
- if (cl->peeklen > 0)
- {
- sz = min(cl->peeklen, len);
- len -= sz; cl->peeklen -= sz;
- memcpy(buf, cl->peekbuf, sz);
- memmove(cl->peekbuf, &cl->peekbuf[sz], cl->peeklen);
- }
-
- /* caller wants more */
- if (len > 0)
- {
+ int seconds = cl->server->conf->network_timeout;
#ifdef HAVE_TLS
- if (cl->tls)
- rsz = cl->server->conf->tls_recv(cl, (void *)&buf[sz], len);
- else
+ if (cl->tls)
+ return __uh_raw_recv(cl, buf, len, seconds,
+ cl->server->conf->tls_recv);
#endif
- rsz = uh_tcp_recv_lowlevel(cl, (void *)&buf[sz], len);
-
- if (rsz < 0)
- return rsz;
-
- sz += rsz;
- }
-
- return sz;
+ return __uh_raw_recv(cl, buf, len, seconds, uh_tcp_recv_lowlevel);
}
@@ -841,8 +892,9 @@ struct listener * uh_listener_add(int sock, struct config *conf)
{
memset(new, 0, sizeof(struct listener));
- new->socket = sock;
- new->conf = conf;
+ new->fd.fd = sock;
+ new->conf = conf;
+
/* get local endpoint addr */
sl = sizeof(struct sockaddr_in6);
@@ -863,7 +915,7 @@ struct listener * uh_listener_lookup(int sock)
struct listener *cur = NULL;
for (cur = uh_listeners; cur; cur = cur->next)
- if (cur->socket == sock)
+ if (cur->fd.fd == sock)
return cur;
return NULL;
@@ -879,7 +931,7 @@ struct client * uh_client_add(int sock, struct listener *serv)
{
memset(new, 0, sizeof(struct client));
- new->socket = sock;
+ new->fd.fd = sock;
new->server = serv;
/* get remote endpoint addr */
@@ -894,6 +946,8 @@ struct client * uh_client_add(int sock, struct listener *serv)
new->next = uh_clients;
uh_clients = new;
+
+ serv->n_clients++;
}
return new;
@@ -904,26 +958,50 @@ struct client * uh_client_lookup(int sock)
struct client *cur = NULL;
for (cur = uh_clients; cur; cur = cur->next)
- if (cur->socket == sock)
+ if (cur->fd.fd == sock)
return cur;
return NULL;
}
-void uh_client_remove(int sock)
+void uh_client_shutdown(struct client *cl)
+{
+#ifdef HAVE_TLS
+ /* free client tls context */
+ if (cl->server && cl->server->conf->tls)
+ cl->server->conf->tls_close(cl);
+#endif
+
+ /* remove from global client list */
+ uh_client_remove(cl);
+}
+
+void uh_client_remove(struct client *cl)
{
struct client *cur = NULL;
struct client *prv = NULL;
for (cur = uh_clients; cur; prv = cur, cur = cur->next)
{
- if (cur->socket == sock)
+ if ((cur == cl) || (!cl && cur->dead))
{
if (prv)
prv->next = cur->next;
else
uh_clients = cur->next;
+ if (cur->timeout.pending)
+ uloop_timeout_cancel(&cur->timeout);
+
+ if (cur->proc.pid)
+ uloop_process_delete(&cur->proc);
+
+ uloop_fd_delete(&cur->fd);
+ close(cur->fd.fd);
+
+ D("IO: Socket(%d) closing\n", cur->fd.fd);
+ cur->server->n_clients--;
+
free(cur);
break;
}