From f71f61f797ff7c4df8c3d057231567c4b8f53d8a Mon Sep 17 00:00:00 2001 From: Mattias Andrée Date: Sat, 21 Oct 2017 17:37:24 +0200 Subject: First commit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mattias Andrée --- .gitignore | 8 + LICENSE | 15 ++ Makefile | 43 +++++ arg.h | 45 +++++ config.mk | 6 + libsbus.c | 86 ++++++++++ libsbus.h | 38 ++++ sbusd.c | 572 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 813 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 arg.h create mode 100644 config.mk create mode 100644 libsbus.c create mode 100644 libsbus.h create mode 100644 sbusd.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bb72c58 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +*~ +*\#* +*.o +*.a +*.so +*.su +*.out +/sbusd diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..2553a69 --- /dev/null +++ b/LICENSE @@ -0,0 +1,15 @@ +ISC License + +© 2017 Mattias Andrée + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..65566cb --- /dev/null +++ b/Makefile @@ -0,0 +1,43 @@ +.POSIX: + +LIB_MAJOR = 1 +LIB_MINOR = 0 + +CONFIGFILE = config.mk +include $(CONFIGFILE) + +all: sbusd libsbus.so libsbus.a + +sbusd.o: arg.h +libsbus.o: libsbus.h + +libsbus.so: libsbus.o + $(CC) -shared -Wl,-soname,libsbus.so.$(LIB_MAJOR) -o $@ $^ $(LDFLAGS) + +libsbus.a: libsbus.o + $(AR) rc $@ $? + $(AR) -s $@ + +install: sbusd libsbus.a libsbus.so + mkdir -p -- "$(DESTDIR)$(PREFIX)/bin" + mkdir -p -- "$(DESTDIR)$(PREFIX)/lib" + mkdir -p -- "$(DESTDIR)$(PREFIX)/share/licenses/sbus" + cp -- sbusd "$(DESTDIR)$(PREFIX)/bin/" + cp -- libsbus.a "$(DESTDIR)$(PREFIX)/lib/" + cp -- libsbus.so "$(DESTDIR)$(PREFIX)/lib/libsbus.so.$(LIB_MAJOR)" + ln -sf -- libsbus.so.$(LIB_MAJOR) "$(DESTDIR)$(PREFIX)/lib/libsbus.so" + ln -sf -- libsbus.so.$(LIB_MAJOR) "$(DESTDIR)$(PREFIX)/lib/libsbus.so.$(LIB_MAJOR).$(LIB_MINOR)" + cp -- LICENSE "$(DESTDIR)$(PREFIX)/share/licenses/sbus/" + +uninstall: + -rm -f -- "$(DESTDIR)$(PREFIX)/bin/sbusd" + -rm -f -- "$(DESTDIR)$(PREFIX)/lib/libsbus.a" + -rm -f -- "$(DESTDIR)$(PREFIX)/lib/libsbus.so" + -rm -f -- "$(DESTDIR)$(PREFIX)/lib/libsbus.so.$(LIB_MAJOR)" + -rm -f -- "$(DESTDIR)$(PREFIX)/lib/libsbus.so.$(LIB_MAJOR).$(LIB_MINOR)" + -rm -rf -- "$(DESTDIR)$(PREFIX)/share/licenses/sbus" + +clean: + -rm -f -- sbusd *.o *.so *.a + +.PHONY: all install uninstall clean diff --git a/arg.h b/arg.h new file mode 100644 index 0000000..81fd8fa --- /dev/null +++ b/arg.h @@ -0,0 +1,45 @@ +/* + * Copy me if you can. + * by 20h + */ + +#ifndef ARG_H__ +#define ARG_H__ + +extern char *argv0; + +/* use main(int argc, char *argv[]) */ +#define ARGBEGIN for (argv0 = *argv, argv++, argc--;\ + argv[0] && argv[0][0] && argv[0][1];\ + argc--, argv++) {\ + char argc_;\ + char **argv_;\ + int brk_;\ + if (argv[0][0] == '-') {\ + if (argv[0][1] == '-' && argv[0][2] == '\0') {\ + argv++;\ + argc--;\ + break;\ + }\ + for (brk_ = 0, argv[0]++, argv_ = argv;\ + argv[0][0] && !brk_;\ + argv[0]++) {\ + if (argv_ != argv)\ + break;\ + argc_ = argv[0][0];\ + switch (argc_) + +#define ARGEND }\ + } else {\ + break;\ + }\ + } + +#define EARGF() ((argv[0][1] == '\0' && argv[1] == NULL)?\ + (usage(), (char *)0) :\ + (brk_ = 1, (argv[0][1] != '\0')?\ + (&argv[0][1]) :\ + (argc--, argv++, argv[0]))) + + +#endif diff --git a/config.mk b/config.mk new file mode 100644 index 0000000..44b4bfc --- /dev/null +++ b/config.mk @@ -0,0 +1,6 @@ +PREFIX = /usr/local +MANPREFIX = $(PREFIX)/share/man + +CFLAGS = -std=c99 -Wall -Wextra -O2 -fPIC +CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_GNU_SOURCE +LDFLAGS = -s diff --git a/libsbus.c b/libsbus.c new file mode 100644 index 0000000..bbd0b4f --- /dev/null +++ b/libsbus.c @@ -0,0 +1,86 @@ +/* See LICENSE file for copyright and license details. */ +#include "libsbus.h" +#include +#include +#include + +int +libsbus_subscribe(int fd, const char *pattern, char *buf) +{ + size_t n = strlen(pattern); + if (n + 4 > LIBSBUS_BUFFER_SIZE) { + errno = EMSGSIZE; + return -1; + } + buf[0] = 'S', buf[1] = 'U', buf[2] = 'B', buf[3] = ' '; + memcpy(&buf[4], pattern, n); + return -(send(fd, buf, n + 4, 0) < 0); +} + +int +libsbus_unsubscribe(int fd, const char *pattern, char *buf) +{ + size_t n = strlen(pattern); + if (n + 6 > LIBSBUS_BUFFER_SIZE) { + errno = EMSGSIZE; + return -1; + } + buf[0] = 'U', buf[1] = 'N', buf[2] = 'S', buf[3] = 'U', buf[4] = 'B', buf[5] = ' '; + memcpy(&buf[6], pattern, n); + return -(send(fd, buf, n + 6, 0) < 0); +} + +int +libsbus_publish(int fd, const char *key, const char *msg, size_t n, char *buf) +{ + size_t len = strlen(key) + 1; + if (len + n > LIBSBUS_BUFFER_SIZE - 4) { + errno = EMSGSIZE; + return -1; + } + buf[0] = 'M', buf[1] = 'S', buf[2] = 'G', buf[3] = ' '; + memcpy(&buf[4], key, len); + memcpy(&buf[4 + len], msg, n); + return -(send(fd, buf, len + n + 4, 0) < 0); +} + +ssize_t +libsbuf_prepare_message(const char *key, char *buf, size_t *remaining) +{ + size_t len = strlen(key) + 1; + if (len > LIBSBUS_BUFFER_SIZE - 4) { + errno = EMSGSIZE; + return -1; + } + buf[0] = 'M', buf[1] = 'S', buf[2] = 'G', buf[3] = ' '; + memcpy(&buf[4], key, len); + len += 4; + *remaining = LIBSBUS_BUFFER_SIZE - len; + return (ssize_t)len; +} + +int +libsbus_receive(int fd, char *buf, union libsbus_packet *packet) +{ + ssize_t r; + char *p; + + r = recv(fd, buf, LIBSBUS_BUFFER_SIZE, 0); + if (r < 0) + return -1; + + if (!strncmp(buf, "MSG ", 4)) { + p = memchr(buf, '\0', r); + if (!*p++) + goto unknown; + packet->type = LIBSBUS_MESSAGE; + packet->message.key = &buf[4]; + packet->message.msg = p; + packet->message.n = (size_t)(r - (p - buf)); + } else { + unknown: + packet->type = LIBSBUS_UNKNOWN; + packet->unknown.n = (size_t)r; + } + return 0; +} diff --git a/libsbus.h b/libsbus.h new file mode 100644 index 0000000..98bf745 --- /dev/null +++ b/libsbus.h @@ -0,0 +1,38 @@ +/* See LICENSE file for copyright and license details. */ +#ifndef LIBSBUS_H +#define LIBSBUS_H + +#include + +#define LIBSBUS_BUFFER_SIZE ((3 << 17) - 1) + +enum libsbus_packet_type { + LIBSBUS_UNKNOWN, + LIBSBUS_MESSAGE +}; + +struct libsbus_unknown { + enum libsbus_packet_type type; + size_t n; +}; + +struct libsbus_message { + enum libsbus_packet_type type; + char *key; + char *msg; + size_t n; +}; + +union libsbus_packet { + enum libsbus_packet_type type; + struct libsbus_unknown unknown; + struct libsbus_message message; +}; + +int libsbus_subscribe(int fd, const char *pattern, char *buf); +int libsbus_unsubscribe(int fd, const char *pattern, char *buf); +int libsbus_publish(int fd, const char *key, const char *msg, size_t n, char *buf); +ssize_t libsbuf_prepare_message(const char *key, char *buf, size_t *remaining); +int libsbus_receive(int fd, char *buf, union libsbus_packet *packet); + +#endif diff --git a/sbusd.c b/sbusd.c new file mode 100644 index 0000000..6367004 --- /dev/null +++ b/sbusd.c @@ -0,0 +1,572 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "arg.h" + +#define STYPE_MAX(T) (long long int)((1ULL << (8 * sizeof(T) - 1)) - 1) +#define eprintf(...) (weprintf(__VA_ARGS__), exit(1)) + +struct client { + int fd; + char **subs; + size_t nsubs; + size_t subs_siz; + struct client *prev; + struct client *next; +}; + +char *argv0; +static struct client head; +static struct client tail; +static int epfd; +static int had_client = 0; +static struct sockaddr_un addr; +static uid_t *users; +static size_t nusers; + +static void +usage(void) +{ + fprintf(stderr, "usage: %s [-a address] [-f | -p pidfile] [-u user] ... [-cgor]\n", argv0); + exit(1); +} + +static void +weprintf(const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + vfprintf(stderr, fmt, args); + if (strchr(fmt, '\0')[-1] == ':') { + fputc(' ', stderr); + perror(NULL); + } + va_end(args); +} + +static void +sigexit(int signo) +{ + if (*addr.sun_path) + unlink(addr.sun_path); + exit(0); + (void) signo; +} + +static struct client * +add_client(int fd) +{ + struct client *cl; + cl = malloc(sizeof(*cl)); + if (!cl) + return NULL; + cl->fd = fd; + cl->subs = NULL; + cl->nsubs = 0; + cl->subs_siz = 0; + cl->next = &tail; + cl->prev = tail.prev; + tail.prev->next = cl; + tail.prev = cl; + return cl; +} + +static void +remove_client(struct client *cl) +{ + close(cl->fd); + cl->prev->next = cl->next; + cl->next->prev = cl->prev; + while (cl->nsubs--) + free(cl->subs[cl->nsubs]); + free(cl->subs); + free(cl); +} + +static void +accept_client(int fd) +{ + struct ucred cred; + struct epoll_event ev; + size_t i; + if (fd < 0) { + weprintf("accept :"); + return; + } + if (nusers) { + if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cred, &(socklen_t){sizeof(cred)}) < 0) { + weprintf("getsockopt SOL_SOCKET SO_PEERCRED:"); + close(fd); + return; + } + for (i = nusers; i--;) + if (users[i] == cred.uid) + goto cred_ok; + weprintf("rejected connection from user %li\n", (long int)cred.uid); + close(fd); + return; + } +cred_ok: + ev.events = EPOLLIN | EPOLLRDHUP; + ev.data.ptr = add_client(fd); + if (!ev.data.ptr) { + close(fd); + } else if (epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev)) { + weprintf("epoll_ctl EPOLL_CTL_ADD :"); + remove_client((void *)ev.data.ptr); + } else { + had_client = 1; + } +} + +static int +is_subscription_match(const char *sub, const char *key) +{ + const char *sub_start = sub; + for (;;) { + while (*sub && *sub == *key) { + sub++; + key++; + } + if (!*key) + return !*sub; + if (!*sub) + return sub == sub_start || sub[-1] == '.'; + if (*sub == '*') { + sub++; + while (*key && *key != '.') + key++; + continue; + } + return 0; + } +} + +static int +is_subscribed(const struct client *cl, const char *key) +{ + size_t i = cl->nsubs; + while (i--) + if (is_subscription_match(cl->subs[i], key)) + return 1; + return 0; +} + +static void +add_subscription(struct client *cl, const char *key) +{ + size_t n; + char **new, *k; + if (cl->subs_siz == cl->nsubs) { + n = cl->subs_siz ? (cl->subs_siz << 1) : 1; + new = realloc(cl->subs, n * sizeof(char *)); + if (!new) { + weprintf("realloc:"); + remove_client(cl); + return; + } + cl->subs = new; + cl->subs_siz = n; + } + k = strdup(key); + if (!k) { + weprintf("strdup:"); + remove_client(cl); + return; + } + cl->subs[cl->nsubs++] = k; +} + +static void +remove_subscription(struct client *cl, const char *key) +{ + size_t i = cl->nsubs; + char **new; + while (i--) { + if (!strcmp(key, cl->subs[i])) { + free(cl->subs[i]); + memmove(&cl->subs[i], &cl->subs[i + 1], --(cl->nsubs) - i); + if (cl->subs_siz >= 4 * cl->nsubs) { + new = realloc(cl->subs, cl->nsubs * sizeof(char *)); + if (new) { + cl->subs_siz = cl->nsubs; + cl->subs = new; + } + } + break; + } + } +} + +static void +broadcast(const char *msg, size_t n) +{ + struct client *cl = head.next, *tmp; + for (; cl->next; cl = cl->next) { + if (!is_subscribed(cl, &msg[4])) + continue; + if (send(cl->fd, msg, n, 0) < 0) { /* TODO queue instead of block */ + cl = (tmp = cl)->prev; + weprintf("send :"); + remove_client(tmp); + } + } +} + +static void +handle_message(struct client *cl) +{ + static char buf[3 << 17]; + int fd = cl->fd; + ssize_t r; + + r = recv(fd, buf, sizeof(buf) - 1, 0); + if (r < 0) { + weprintf("recv :"); + remove_client(cl); + return; + } + buf[r] = '\0'; + + if (!strncmp(buf, "MSG ", 4)) { + broadcast(buf, r); + } else if (!strncmp(buf, "UNSUB ", 6)) { + remove_subscription(cl, &buf[6]); + } else if (!strncmp(buf, "SUB ", 4)) { + add_subscription(cl, &buf[4]); + } else { + weprintf("received bad message\n"); + remove_client(cl); + } +} + +static void +randomise(void *buf, size_t n) +{ + char *p = buf; + while (n--) + *p++ = rand(); +} + +static void +print_address(void) +{ + char buf[2 * sizeof(addr.sun_path) + 1]; + char *p = buf; + const unsigned char *a = (const unsigned char *)addr.sun_path; + size_t n = sizeof(addr.sun_path); + + for (; n--; p += 2, a += 1) { + p[0] = "0123456789abcdef"[(int)*a >> 4]; + p[1] = "0123456789abcdef"[(int)*a & 15]; + } + *p = '\0'; + + printf("/dev/unix/abstract/%s\n", buf); + if (ferror(stderr)) + eprintf("failed print generated address:"); +} + +static int +make_socket(const char *address, int reuse, mode_t mode) +{ + int fd = -1, randaddr = 0, hi, lo, listening = 0; + long int tmp; + size_t n; + const char *p, *q; + char *a; + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + + if (strstr(address, "/dev/fd/") == address) { + p = &address[sizeof("/dev/fd/") - 1]; + if (isdigit(*p)) + goto def; + errno = 0; + tmp = strtol(p, &a, 10); + if (errno || *a || tmp < 0) { + errno = 0; + goto def; + } + if (tmp > INT_MAX) { + errno = EBADF; + goto bad_address; + } + fd = (int)tmp; + reuse = 0; + } else if (!strcmp(address, "/dev/unix/abstract")) { + randaddr = 1; + reuse = 0; + } else if (strstr(address, "/dev/unix/abstract/") == address) { + p = &address[sizeof("/dev/unix/abstract/") - 1]; + n = strlen(p); + if (n & 1) + goto def; + for (q = p; *q; q++) + if (!isxdigit(*q)) + goto def; + if (n > sizeof(addr.sun_path) * 2) { + errno = ENAMETOOLONG; + goto bad_address; + } + a = addr.sun_path; + for (; *p; p += 2) { + hi = (p[0] & 15) + 9 * !isdigit(p[0]); + lo = (p[1] & 15) + 9 * !isdigit(p[1]); + *a++ = (hi << 4) | lo; + } + reuse = 0; + } else { + def: + if (strlen(address) >= sizeof(addr.sun_path)) { + errno = ENAMETOOLONG; + goto bad_address; + } + strcpy(addr.sun_path, address); + } + + if (reuse) + unlink(addr.sun_path); + + if (fd < 0) { + fd = socket(PF_UNIX, SOCK_SEQPACKET, 0); + if (fd < 0) + eprintf("socket PF_UNIX SOCK_SEQPACKET:"); + if (fchmod(fd, mode)) + eprintf("fchmod %o:", mode); + if (randaddr) { + srand((unsigned)time(NULL)); + for (;;) { + randomise(&addr.sun_path[1], sizeof(addr.sun_path) - 1); + if (!bind(fd, (void *)&addr, sizeof(addr))) + break; + else if (errno != EADDRINUSE) + eprintf("bind :"); + } + print_address(); + } else { + if (bind(fd, (void *)&addr, sizeof(addr))) { + if (*addr.sun_path) + eprintf("bind %s:", addr.sun_path); + else + eprintf("bind :", &address[sizeof("/dev/unix/abstract/") - 1]); + } + } + } else { + if (mode & 0070) + weprintf("ignoring -g due to using passed down socket\n"); + if (mode & 0007) + weprintf("ignoring -o due to using passed down socket\n"); + if (getsockopt(fd, SOL_SOCKET, SO_ACCEPTCONN, &listening, &(socklen_t){sizeof(listening)})) + eprintf("getsockopt SOL_SOCKET SO_ACCEPTCONN:"); + } + + if (!listening && listen(fd, SOMAXCONN)) + eprintf("listen:"); + + return fd; + +bad_address: + eprintf("bad unix socket address:"); + exit(1); +} + +static void +daemonise(const char *pidfile) +{ + pid_t pid; + int rw[2], status = 0, fd; + FILE *fp; + + if (pipe(rw)) + eprintf("pipe:"); + + switch ((pid = fork())) { + case -1: + eprintf("fork:"); + + case 0: + close(rw[0]); + setsid(); + switch (fork()) { + case -1: + eprintf("fork:"); + + case 0: + if (signal(SIGHUP, SIG_IGN) == SIG_ERR) + weprintf("signal SIGHUP SIG_IGN:"); + if (signal(SIGINT, sigexit) == SIG_ERR) + weprintf("signal SIGINT :"); + if (strcmp(pidfile, "/dev/null")) { + pid = getpid(); + fd = open(pidfile, O_WRONLY | O_CREAT | O_EXCL, 0644); + if (fd < 0) + eprintf("open %s O_WRONLY O_CREAT O_EXCL:", pidfile); + fp = fdopen(fd, "w"); + fprintf(fp, "%li\n", (long int)pid); + if (fflush(fp) || ferror(fp)) + eprintf("fprintf %s:", pidfile); + fclose(fp); + } + if (chdir("/")) + eprintf("chdir /:"); + close(STDIN_FILENO); + close(STDOUT_FILENO); + if (isatty(STDERR_FILENO)) { + fd = open("/dev/null", O_WRONLY); + if (fd) + eprintf("open /dev/null O_WRONLY:"); + if (dup2(fd, STDERR_FILENO) != STDERR_FILENO) + eprintf("dup2 /dev/null /dev/stderr:"); + close(fd); + } + if (write(rw[1], &status, 1) < 1) + eprintf("write :"); + close(rw[1]); + break; + + default: + exit(0); + } + break; + + default: + close(rw[1]); + if (waitpid(pid, &status, 0) != pid) + eprintf("waitpid:"); + if (status) + exit(1); + switch (read(rw[0], &status, 1)) { + case -1: + eprintf("read :"); + case 0: + exit(1); + default: + exit(0); + } + } +} + +int +main(int argc, char *argv[]) +{ + struct epoll_event evs[32]; + const char *address = "/run/sbus.socket"; + const char *pidfile = "/run/sbus.pid"; + int auto_close = 0; + int foreground = 0; + mode_t mode = 0700; + int reuse_address = 0; + struct passwd *user; + int server, n; + long long int tmp; + char *arg; + + users = alloca(argc * sizeof(*users)); + + ARGBEGIN { + case 'a': + address = EARGF(); + break; + case 'c': + auto_close = 1; + break; + case 'f': + foreground = 1; + break; + case 'g': + mode |= 0070; + break; + case 'o': + mode |= 0007; + break; + case 'p': + pidfile = EARGF(); + break; + case 'r': + reuse_address = 1; + break; + case 'u': + arg = EARGF(); + if (!isdigit(*arg)) + goto user_by_name; + errno = 0; + tmp = strtoll(arg, &arg, 10); + if (errno || *arg || tmp < 0 || tmp > STYPE_MAX(uid_t)) + goto user_by_name; + users[nusers++] = (uid_t)tmp; + user_by_name: + user = getpwnam(arg); + if (!user) + eprintf("getpwnam %s:", arg); + users[nusers++] = user->pw_uid; + break; + default: + usage(); + } ARGEND; + if (argc) + usage(); + + umask(0); + server = make_socket(address, reuse_address, mode); + if (foreground) { + close(0); + close(1); + if (signal(SIGHUP, sigexit) == SIG_ERR) + weprintf("signal SIGHUP :"); + if (signal(SIGINT, sigexit) == SIG_ERR) + weprintf("signal SIGINT :"); + } else { + daemonise(pidfile); + } + + if (nusers) + users[nusers++] = getuid(); + + head.next = &tail; + tail.prev = &head; + + epfd = epoll_create1(0); + if (epfd < 0) + eprintf("epoll_create1:"); + + evs->events = EPOLLIN; + evs->data.ptr = NULL; + if (epoll_ctl(epfd, EPOLL_CTL_ADD, server, evs)) + eprintf("epoll_ctl EPOLL_CTL_ADD :"); + + while (!auto_close || !had_client || head.next->next) { + n = epoll_wait(epfd, evs, sizeof(evs) / sizeof(*evs), -1); + if (n < 0) + eprintf("epoll_wait:"); + while (n--) { + if (!evs[n].data.ptr) + accept_client(accept(server, NULL, NULL)); + else if (evs[n].events & (EPOLLRDHUP | EPOLLHUP)) + remove_client((void *)evs[n].data.ptr); + else + handle_message((void *)evs[n].data.ptr); + } + } + + return 0; +} -- cgit v1.2.3-70-g09d2