Add new package sse-multiplex
This commit is contained in:
parent
c1aa8b847b
commit
3f78449b9d
|
@ -0,0 +1,36 @@
|
||||||
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
|
PKG_NAME:=sse-multiplex
|
||||||
|
PKG_VERSION:=1
|
||||||
|
|
||||||
|
PKG_LICENSE:=BSD-2-Clause
|
||||||
|
|
||||||
|
PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
|
||||||
|
|
||||||
|
include $(INCLUDE_DIR)/package.mk
|
||||||
|
include $(INCLUDE_DIR)/cmake.mk
|
||||||
|
|
||||||
|
define Package/sse-multiplex
|
||||||
|
SECTION:=net
|
||||||
|
CATEGORY:=Network
|
||||||
|
DEPENDS:=
|
||||||
|
TITLE:=Allows multiple clients to receive the same Server-Sent Event stream
|
||||||
|
endef
|
||||||
|
|
||||||
|
CMAKE_OPTIONS += -DCMAKE_BUILD_TYPE:STRING=MINSIZEREL -DSSE_MULTIPLEX_SOCKET:STRING=/var/run/sse-multiplex.sock
|
||||||
|
|
||||||
|
define Build/Prepare
|
||||||
|
mkdir -p $(PKG_BUILD_DIR)
|
||||||
|
$(CP) ./src/* $(PKG_BUILD_DIR)/
|
||||||
|
endef
|
||||||
|
|
||||||
|
define Package/sse-multiplex/install
|
||||||
|
$(INSTALL_DIR) $(1)/usr/sbin
|
||||||
|
$(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/sbin/sse-multiplexd $(1)/usr/sbin/
|
||||||
|
$(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/sbin/sse-multiplex $(1)/usr/sbin/
|
||||||
|
|
||||||
|
$(INSTALL_DIR) $(1)/etc/init.d
|
||||||
|
$(INSTALL_BIN) ./files/sse-multiplexd.init $(1)/etc/init.d/sse-multiplexd
|
||||||
|
endef
|
||||||
|
|
||||||
|
$(eval $(call BuildPackage,sse-multiplex))
|
|
@ -0,0 +1,15 @@
|
||||||
|
#!/bin/sh /etc/rc.common
|
||||||
|
|
||||||
|
START=50
|
||||||
|
|
||||||
|
SERVICE_WRITE_PID=1
|
||||||
|
SERVICE_DAEMONIZE=1
|
||||||
|
|
||||||
|
|
||||||
|
start() {
|
||||||
|
service_start /usr/sbin/sse-multiplexd
|
||||||
|
}
|
||||||
|
|
||||||
|
stop() {
|
||||||
|
service_stop /usr/sbin/sse-multiplexd
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
cmake_minimum_required(VERSION 2.6)
|
||||||
|
|
||||||
|
project(sse-multiplex C)
|
||||||
|
|
||||||
|
set(SSE_MULTIPLEX_SOCKET "/run/sse-multiplex.sock" CACHE STRING "Path of the socket to use")
|
||||||
|
|
||||||
|
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/sse-multiplex.h.in ${CMAKE_BINARY_DIR}/gen/generated/sse-multiplex.h)
|
||||||
|
|
||||||
|
set_property(DIRECTORY PROPERTY COMPILE_DEFINITIONS _GNU_SOURCE)
|
||||||
|
include_directories(${CMAKE_BINARY_DIR}/gen)
|
||||||
|
|
||||||
|
add_executable(sse-multiplexd sse-multiplexd.c)
|
||||||
|
set_property(TARGET sse-multiplexd PROPERTY COMPILE_FLAGS "-Wall -std=c99")
|
||||||
|
|
||||||
|
add_executable(sse-multiplex sse-multiplex.c)
|
||||||
|
set_property(TARGET sse-multiplex PROPERTY COMPILE_FLAGS "-Wall -std=c99")
|
||||||
|
|
||||||
|
install(TARGETS sse-multiplexd sse-multiplex RUNTIME DESTINATION sbin)
|
|
@ -0,0 +1,99 @@
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright (c) 2015, Matthias Schiffer <mschiffer@universe-factory.net>
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer.
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <generated/sse-multiplex.h>
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <sys/un.h>
|
||||||
|
|
||||||
|
|
||||||
|
int main(int argc, char *argv[]) {
|
||||||
|
if (argc != 2) {
|
||||||
|
fprintf(stderr, "Usage: %s <command>\n", argv[0]);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t socket_len = strlen(SSE_MULTIPLEX_SOCKET);
|
||||||
|
size_t len = offsetof(struct sockaddr_un, sun_path) + socket_len + 1;
|
||||||
|
uint8_t addrbuf[len];
|
||||||
|
memset(addrbuf, 0, len);
|
||||||
|
|
||||||
|
struct sockaddr_un *sa = (void*)addrbuf;
|
||||||
|
|
||||||
|
sa->sun_family = AF_UNIX;
|
||||||
|
memcpy(sa->sun_path, SSE_MULTIPLEX_SOCKET, socket_len+1);
|
||||||
|
|
||||||
|
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||||
|
if (fd < 0) {
|
||||||
|
fprintf(stderr, "Failed to create socket: %s\n", strerror(errno));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (connect(fd, (struct sockaddr*)sa, sizeof(addrbuf)) < 0) {
|
||||||
|
fprintf(stderr, "Can't connect to `%s': %s\n", SSE_MULTIPLEX_SOCKET, strerror(errno));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *command = argv[1];
|
||||||
|
while (command[0]) {
|
||||||
|
ssize_t w = write(fd, command, strlen(command));
|
||||||
|
if (w < 0) {
|
||||||
|
fprintf(stderr, "Can't write command: %s\n", strerror(errno));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
command += w;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shutdown(fd, SHUT_WR) < 0) {
|
||||||
|
fprintf(stderr, "shutdown: %s\n", strerror(errno));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
setlinebuf(stdout);
|
||||||
|
|
||||||
|
char buf[1024];
|
||||||
|
ssize_t r;
|
||||||
|
while (1) {
|
||||||
|
r = recv(fd, buf, sizeof(buf), 0);
|
||||||
|
if (r < 0) {
|
||||||
|
fprintf(stderr, "read: %s\n", strerror(errno));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (r == 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
fwrite(buf, r, 1, stdout);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
|
||||||
|
#define SSE_MULTIPLEX_SOCKET "@SSE_MULTIPLEX_SOCKET@"
|
|
@ -0,0 +1,460 @@
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright (c) 2015, Matthias Schiffer <mschiffer@universe-factory.net>
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer.
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <generated/sse-multiplex.h>
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <syslog.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/epoll.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <sys/un.h>
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct client client_t;
|
||||||
|
typedef struct provider provider_t;
|
||||||
|
|
||||||
|
|
||||||
|
static volatile bool running = true;
|
||||||
|
|
||||||
|
static int epoll_fd = -1;
|
||||||
|
static int listen_fd = -1;
|
||||||
|
static struct epoll_event listen_event = {};
|
||||||
|
|
||||||
|
static provider_t *providers = NULL;
|
||||||
|
|
||||||
|
|
||||||
|
struct client {
|
||||||
|
struct client *next;
|
||||||
|
|
||||||
|
FILE *file;
|
||||||
|
bool active;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct provider {
|
||||||
|
struct provider *prev;
|
||||||
|
struct provider *next;
|
||||||
|
|
||||||
|
char *command;
|
||||||
|
FILE *file;
|
||||||
|
struct epoll_event event;
|
||||||
|
|
||||||
|
char *header;
|
||||||
|
bool preclean;
|
||||||
|
bool clean;
|
||||||
|
|
||||||
|
client_t *clients;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
static char * read_header(FILE *file) {
|
||||||
|
size_t buflen = 256, content_len = 0;
|
||||||
|
char *buffer = malloc(buflen);
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
size_t space = buflen - content_len;
|
||||||
|
if (space < 128) {
|
||||||
|
buflen += 256;
|
||||||
|
buffer = realloc(buffer, buflen);
|
||||||
|
space = buflen - content_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ok = fgets(buffer+content_len, space, file);
|
||||||
|
if (!ok) {
|
||||||
|
free(buffer);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
content_len += strlen(buffer+content_len);
|
||||||
|
|
||||||
|
if (content_len >= 2 && buffer[content_len-2] == '\n' && buffer[content_len-1] == '\n')
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static FILE * run_command(const char *command) {
|
||||||
|
int pipefd[2];
|
||||||
|
if (pipe(pipefd) < 0) {
|
||||||
|
syslog(LOG_ERR, "pipe: %s", strerror(errno));
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
pid_t pid = fork();
|
||||||
|
if (pid < 0) {
|
||||||
|
syslog(LOG_ERR, "fork: %s", strerror(errno));
|
||||||
|
close(pipefd[0]);
|
||||||
|
close(pipefd[1]);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pid > 0) {
|
||||||
|
close(pipefd[1]);
|
||||||
|
|
||||||
|
FILE *file = fdopen(pipefd[0], "r");
|
||||||
|
if (!file) {
|
||||||
|
close(pipefd[0]);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
close(pipefd[0]);
|
||||||
|
dup2(pipefd[1], STDOUT_FILENO);
|
||||||
|
|
||||||
|
if (pipefd[1] != STDOUT_FILENO)
|
||||||
|
close(pipefd[1]);
|
||||||
|
|
||||||
|
struct sigaction action = {};
|
||||||
|
sigemptyset(&action.sa_mask);
|
||||||
|
|
||||||
|
action.sa_handler = SIG_DFL;
|
||||||
|
sigaction(SIGCHLD, &action, NULL);
|
||||||
|
sigaction(SIGPIPE, &action, NULL);
|
||||||
|
|
||||||
|
execl("/bin/sh", "/bin/sh", "-c", command, NULL);
|
||||||
|
_exit(127);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static provider_t * new_provider(const char *command) {
|
||||||
|
FILE *file = run_command(command);
|
||||||
|
if (!file) {
|
||||||
|
syslog(LOG_WARNING, "unable to start provider `%s'", command);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *header = read_header(file);
|
||||||
|
if (!header) {
|
||||||
|
fclose(file);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
fcntl(fileno(file), F_SETFL, fcntl(fileno(file), F_GETFL) | O_NONBLOCK);
|
||||||
|
|
||||||
|
provider_t *p = calloc(1, sizeof(*p));
|
||||||
|
p->command = strdup(command);
|
||||||
|
p->file = file;
|
||||||
|
p->header = header;
|
||||||
|
p->preclean = true;
|
||||||
|
p->clean = true;
|
||||||
|
|
||||||
|
p->event.events = EPOLLIN|EPOLLRDHUP;
|
||||||
|
p->event.data.ptr = p;
|
||||||
|
|
||||||
|
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fileno(file), &p->event) < 0) {
|
||||||
|
fprintf(stderr, "epoll_ctl: %s\n", strerror(errno));
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (providers)
|
||||||
|
providers->prev = p;
|
||||||
|
p->next = providers;
|
||||||
|
providers = p;
|
||||||
|
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
static provider_t * get_provider(const char *command) {
|
||||||
|
provider_t *p;
|
||||||
|
for (p = providers; p; p = p->next) {
|
||||||
|
if (!strcmp(p->command, command))
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new_provider(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void free_clients(client_t *clients) {
|
||||||
|
while (clients) {
|
||||||
|
client_t *next = clients->next;
|
||||||
|
|
||||||
|
fclose(clients->file);
|
||||||
|
free(clients);
|
||||||
|
|
||||||
|
clients = next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void free_provider(provider_t *p) {
|
||||||
|
if (epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fileno(p->file), NULL) < 0) {
|
||||||
|
fprintf(stderr, "epoll_ctl: %s\n", strerror(errno));
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
free(p->command);
|
||||||
|
fclose(p->file);
|
||||||
|
free(p->header);
|
||||||
|
free_clients(p->clients);
|
||||||
|
free(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void remove_provider(provider_t *p) {
|
||||||
|
if (p->next)
|
||||||
|
p->next->prev = p->prev;
|
||||||
|
|
||||||
|
if (p->prev)
|
||||||
|
p->prev->next = p->next;
|
||||||
|
else
|
||||||
|
providers = p->next;
|
||||||
|
|
||||||
|
free_provider(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void add_client(provider_t *p, FILE *file) {
|
||||||
|
if (fputs(p->header, file) == EOF || fflush(file) == EOF) {
|
||||||
|
fclose(file);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
client_t *c = calloc(1, sizeof(*c));
|
||||||
|
c->file = file;
|
||||||
|
c->active = p->clean;
|
||||||
|
|
||||||
|
c->next = p->clients;
|
||||||
|
p->clients = c;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void remove_client(client_t **client) {
|
||||||
|
client_t *c = *client;
|
||||||
|
*client = c->next;
|
||||||
|
|
||||||
|
fclose(c->file);
|
||||||
|
free(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void init_epoll(void) {
|
||||||
|
epoll_fd = epoll_create1(EPOLL_CLOEXEC);
|
||||||
|
if (epoll_fd < 0) {
|
||||||
|
fprintf(stderr, "Unable initialize epoll: %s\n", strerror(errno));
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void unlink_socket(void) {
|
||||||
|
if (listen_fd >= 0) {
|
||||||
|
unlink(SSE_MULTIPLEX_SOCKET);
|
||||||
|
listen_fd = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void create_socket(void) {
|
||||||
|
listen_fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
|
||||||
|
if (listen_fd < 0) {
|
||||||
|
fprintf(stderr, "socket: %s\n", strerror(errno));
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t socket_len = strlen(SSE_MULTIPLEX_SOCKET);
|
||||||
|
size_t len = offsetof(struct sockaddr_un, sun_path) + socket_len + 1;
|
||||||
|
uint8_t buf[len];
|
||||||
|
memset(buf, 0, len);
|
||||||
|
|
||||||
|
struct sockaddr_un *sa = (void*)buf;
|
||||||
|
|
||||||
|
sa->sun_family = AF_UNIX;
|
||||||
|
memcpy(sa->sun_path, SSE_MULTIPLEX_SOCKET, socket_len+1);
|
||||||
|
|
||||||
|
mode_t old_umask = umask(077);
|
||||||
|
|
||||||
|
if (bind(listen_fd, (struct sockaddr*)sa, len)) {
|
||||||
|
switch (errno) {
|
||||||
|
case EADDRINUSE:
|
||||||
|
fprintf(stderr, "Unable to bind socket: the path `%s' already exists\n", SSE_MULTIPLEX_SOCKET);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
fprintf(stderr, "Unable to bind socket: %s\n", strerror(errno));
|
||||||
|
}
|
||||||
|
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
umask(old_umask);
|
||||||
|
|
||||||
|
if (atexit(unlink_socket)) {
|
||||||
|
fprintf(stderr, "atexit: %s", strerror(errno));
|
||||||
|
unlink_socket();
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (listen(listen_fd, 16) < 0) {
|
||||||
|
fprintf(stderr, "listen: %s\n", strerror(errno));
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
listen_event.events = EPOLLIN;
|
||||||
|
listen_event.data.ptr = &listen_event;
|
||||||
|
|
||||||
|
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listen_fd, &listen_event) < 0) {
|
||||||
|
fprintf(stderr, "epoll_ctl: %s\n", strerror(errno));
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void signal_exit(int signal __attribute__((unused))) {
|
||||||
|
running = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void setup_signals(void) {
|
||||||
|
struct sigaction action = {};
|
||||||
|
sigemptyset(&action.sa_mask);
|
||||||
|
|
||||||
|
action.sa_handler = signal_exit;
|
||||||
|
sigaction(SIGINT, &action, NULL);
|
||||||
|
sigaction(SIGTERM, &action, NULL);
|
||||||
|
sigaction(SIGQUIT, &action, NULL);
|
||||||
|
|
||||||
|
action.sa_handler = SIG_IGN;
|
||||||
|
sigaction(SIGCHLD, &action, NULL);
|
||||||
|
sigaction(SIGPIPE, &action, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void handle_data(provider_t *provider) {
|
||||||
|
while (true) {
|
||||||
|
if (feof(provider->file)) {
|
||||||
|
remove_provider(provider);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
char buf[1024];
|
||||||
|
bool ok = fgets(buf, sizeof(buf), provider->file);
|
||||||
|
if (!ok)
|
||||||
|
return;
|
||||||
|
|
||||||
|
provider->clean = provider->preclean && (buf[0] == '\n');
|
||||||
|
provider->preclean = (buf[strlen(buf)-1] == '\n');
|
||||||
|
|
||||||
|
client_t **c = &provider->clients;
|
||||||
|
while (*c) {
|
||||||
|
if ((*c)->active && fputs(buf, (*c)->file) == EOF) {
|
||||||
|
remove_client(c);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (provider->clean) {
|
||||||
|
/* The ferror check should be redundant, as flush
|
||||||
|
* should already return EOF on errors; on uClibc,
|
||||||
|
* it sometimes doesn't... */
|
||||||
|
if (fflush((*c)->file) == EOF || ferror((*c)->file)) {
|
||||||
|
remove_client(c);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
(*c)->active = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
c = &(*c)->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!provider->clients) {
|
||||||
|
remove_provider(provider);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void handle_accept(uint32_t events) {
|
||||||
|
if (events != EPOLLIN) {
|
||||||
|
syslog(LOG_ERR, "unexpected event on listening socket: %u\n", (unsigned)events);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
int fd = accept4(listen_fd, NULL, NULL, SOCK_CLOEXEC);
|
||||||
|
if (fd < 0) {
|
||||||
|
syslog(LOG_WARNING, "accept4: %s\n", strerror(errno));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
FILE *file = fdopen(fd, "r+");
|
||||||
|
if (!file) {
|
||||||
|
syslog(LOG_WARNING, "fdopen: %s\n", strerror(errno));
|
||||||
|
close(fd);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
char command[1024];
|
||||||
|
bool ok = fgets(command, sizeof(command), file);
|
||||||
|
if (!ok || !command[0] || !feof(file)) {
|
||||||
|
fclose(file);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
provider_t *p = get_provider(command);
|
||||||
|
if (!p) {
|
||||||
|
fclose(file);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
add_client(p, file);
|
||||||
|
handle_data(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
void cleanup(void) {
|
||||||
|
while (providers)
|
||||||
|
remove_provider(providers);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
init_epoll();
|
||||||
|
create_socket();
|
||||||
|
setup_signals();
|
||||||
|
|
||||||
|
while (running) {
|
||||||
|
struct epoll_event event;
|
||||||
|
int ret = epoll_wait(epoll_fd, &event, 1, -1);
|
||||||
|
if (ret == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (ret < 0) {
|
||||||
|
if (errno == EINTR)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
syslog(LOG_ERR, "epoll_wait: %s\n", strerror(errno));
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.data.ptr == &listen_event)
|
||||||
|
handle_accept(event.events);
|
||||||
|
else
|
||||||
|
handle_data(event.data.ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup();
|
||||||
|
}
|
Loading…
Reference in New Issue