#include "libweb/server.h" #include #include #include #include #include #include #include #include #include #include #include #include struct server { int fd, cfds[2], lastfd; struct server_client { int fd; bool write; struct server_client *prev, *next; } *c; }; int server_close(struct server *const s) { int ret = 0; if (!s) return 0; else if (s->fd >= 0) ret = close(s->fd); free(s); return ret; } int server_client_close(struct server *const s, struct server_client *const c) { int ret = 0; for (struct server_client *ref = s->c; ref; ref = ref->next) { if (c == ref) { struct server_client *const next = ref->next; if ((ret = close(c->fd))) { fprintf(stderr, "%s: close(2): %s\n", __func__, strerror(errno)); } else if (ref->prev) ref->prev->next = next; else s->c = next; if (next) next->prev = ref->prev; free(ref); break; } } return ret; } int server_read(void *const buf, const size_t n, struct server_client *const c) { const ssize_t r = read(c->fd, buf, n); if (r < 0) fprintf(stderr, "%s: read(2): %s\n", __func__, strerror(errno)); return r; } int server_write(const void *const buf, const size_t n, struct server_client *const c) { const ssize_t w = write(c->fd, buf, n); if (w < 0) fprintf(stderr, "%s: write(2): %s\n", __func__, strerror(errno)); return w; } static struct server_client *alloc_client(struct server *const s) { struct sockaddr_in addr; socklen_t sz = sizeof addr; struct server_client *c = NULL; const int fd = accept(s->fd, (struct sockaddr *)&addr, &sz); if (fd < 0) { fprintf(stderr, "%s: accept(2): %s\n", __func__, strerror(errno)); goto failure; } const int flags = fcntl(fd, F_GETFL); if (flags < 0) { fprintf(stderr, "%s: fcntl(2) F_GETFL: %s\n", __func__, strerror(errno)); goto failure; } else if (fcntl(fd, F_SETFL, flags | O_NONBLOCK)) { fprintf(stderr, "%s: fcntl(2) F_SETFL: %s\n", __func__, strerror(errno)); goto failure; } else if (!(c = malloc(sizeof *c))) { fprintf(stderr, "%s: malloc(3): %s\n", __func__, strerror(errno)); goto failure; } *c = (const struct server_client) { .fd = fd }; if (!s->c) s->c = c; else for (struct server_client *ref = s->c; ref; ref = ref->next) if (!ref->next) { ref->next = c; c->prev = ref; break; } return c; failure: if (fd >= 0 && close(fd)) fprintf(stderr, "%s: close(2): %s\n", __func__, strerror(errno)); free(c); return NULL; } void server_client_write_pending(struct server_client *const c, const bool write) { c->write = write; } static size_t get_clients(const struct server *const s) { size_t ret = 0; for (const struct server_client *c = s->c; c; c = c->next) ret++; return ret; } static int check_exit(const int fd) { char exit; const ssize_t n = read(fd, &exit, sizeof exit); if (n <= 0) { fprintf(stderr, "%s: read(2): %s\n", __func__, strerror(errno)); return -1; } return 0; } struct server_client *server_poll(struct server *const s, bool *const io, bool *const exit) { enum {SERVER, PIPE, CLIENTS}; struct server_client *ret = NULL; const size_t n_clients = get_clients(s); const nfds_t n = n_clients + CLIENTS; struct pollfd *const fds = malloc(n * sizeof *fds); if (!fds) { fprintf(stderr, "%s: malloc(3): %s\n", __func__, strerror(errno)); goto end; } struct pollfd *const sfd = &fds[SERVER], *const pfd = &fds[PIPE]; *io = *exit = false; *sfd = (const struct pollfd) { .fd = s->fd, .events = POLLIN }; *pfd = (const struct pollfd) { .fd = s->cfds[0], .events = POLLIN }; for (struct {const struct server_client *c; size_t j;} _ = {.c = s->c, .j = CLIENTS}; _.c; _.c = _.c->next, _.j++) { struct pollfd *const p = &fds[_.j]; const int fd = _.c->fd; *p = (const struct pollfd) { .fd = fd, .events = POLLIN }; if (_.c->write) p->events |= POLLOUT; } int res; again: res = poll(fds, n, -1); if (res < 0) { switch (errno) { case EAGAIN: /* Fall through. */ case EINTR: goto again; default: fprintf(stderr, "%s: poll(2): %s\n", __func__, strerror(errno)); break; } goto end; } else if (!res) { fprintf(stderr, "%s: poll(2) returned zero\n", __func__); goto end; } else if (pfd->revents) { if (check_exit(pfd->fd)) fprintf(stderr, "%s: check_exit failed\n", __func__); else *exit = true; goto end; } else if (sfd->revents) { ret = alloc_client(s); goto end; } struct server_client *sel = NULL; for (struct {struct server_client *c; size_t j;} _ = {.c = s->c, .j = CLIENTS}; _.c; _.c = _.c->next, _.j++) { const struct pollfd *const p = &fds[_.j]; if (p->revents) { sel = _.c; if (n_clients == 1 || p->fd != s->lastfd) break; } } if (sel) { *io = true; ret = sel; s->lastfd = sel->fd; goto end; } fprintf(stderr, "%s: unlisted fd\n", __func__); end: free(fds); return ret; } struct server *server_init(const unsigned short port, unsigned short *const outport, const unsigned ubacklog) { struct server *const s = malloc(sizeof *s); int fds[2] = {-1, -1}, fd = -1; /* Ensure default value if not set. */ enum {QUEUE_LEN = 10}; const unsigned backlog = ubacklog ? ubacklog : QUEUE_LEN; if (!s) { fprintf(stderr, "%s: malloc(3): %s\n", __func__, strerror(errno)); goto failure; } else if ((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { fprintf(stderr, "%s: socket(2): %s\n", __func__, strerror(errno)); goto failure; } else if (pipe(fds)) { fprintf(stderr, "%s: socketpair(2): %s\n", __func__, strerror(errno)); goto failure; } const struct sockaddr_in addr = { .sin_family = AF_INET, .sin_port = htons(port) }; if (bind(fd, (const struct sockaddr *)&addr, sizeof addr)) { fprintf(stderr, "%s: bind(2): %s\n", __func__, strerror(errno)); goto failure; } else if (backlog > INT_MAX) { fprintf(stderr, "%s: backlog (%u) exceeds maximum (%d)\n", __func__, backlog, INT_MAX); goto failure; } else if (listen(fd, backlog)) { fprintf(stderr, "%s: listen(2): %s\n", __func__, strerror(errno)); goto failure; } struct sockaddr_in in; socklen_t sz = sizeof in; if (getsockname(fd, (struct sockaddr *)&in, &sz)) { fprintf(stderr, "%s: getsockname(2): %s\n", __func__, strerror(errno)); goto failure; } *s = (const struct server) { .fd = fd, .cfds = { [0] = fds[0], [1] = fds[1] } }; if (outport) *outport = ntohs(in.sin_port); return s; failure:; const int a[] = {fd, fds[0], fds[1]}; for (size_t i = 0; i < sizeof a / sizeof *a; i++) { const int fd = a[i]; if (fd >= 0 && close(fd)) fprintf(stderr, "%s: close(2)[%zu]: %s\n", __func__, i, strerror(errno)); } return NULL; } int server_notify_close(struct server *const s) { char exit = 0; return write(s->cfds[1], &exit, sizeof exit) <= 0 ? -1 : 0; }