From 6fd87d0ab057cd52e69356d8f8d4de76ebf62add Mon Sep 17 00:00:00 2001 From: Olaf Rempel Date: Sun, 12 Dec 2010 16:23:33 +0100 Subject: [PATCH] initial version --- .gitignore | 7 ++ Makefile | 45 ++++++++ bcastfwd.c | 256 +++++++++++++++++++++++++++++++++++++++++ bcastfwd.conf | 13 +++ configfile.c | 288 ++++++++++++++++++++++++++++++++++++++++++++++ configfile.h | 28 +++++ event.c | 310 ++++++++++++++++++++++++++++++++++++++++++++++++++ event.h | 44 +++++++ list.h | 268 +++++++++++++++++++++++++++++++++++++++++++ logging.c | 114 +++++++++++++++++++ logging.h | 23 ++++ main.c | 162 ++++++++++++++++++++++++++ pidfile.c | 73 ++++++++++++ pidfile.h | 11 ++ signals.c | 151 ++++++++++++++++++++++++ signals.h | 12 ++ sockaddr.c | 117 +++++++++++++++++++ sockaddr.h | 14 +++ 18 files changed, 1936 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 bcastfwd.c create mode 100644 bcastfwd.conf create mode 100644 configfile.c create mode 100644 configfile.h create mode 100644 event.c create mode 100644 event.h create mode 100644 list.h create mode 100644 logging.c create mode 100644 logging.h create mode 100644 main.c create mode 100644 pidfile.c create mode 100644 pidfile.h create mode 100644 signals.c create mode 100644 signals.h create mode 100644 sockaddr.c create mode 100644 sockaddr.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8c03cc5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +*.o +*.d +bcastfwd +bcastfwd.pid +bcastfwd.log + + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..89b761f --- /dev/null +++ b/Makefile @@ -0,0 +1,45 @@ +DESTDIR = +BINARY_DIR = /usr/local/bin +CONFIG_DIR = /usr/local/etc +LOG_DIR = /var/log +PID_DIR = /var/run + +# ############################ + +SRC := $(wildcard *.c) +TARGET := bcastfwd +CFLAGS := -O2 -Wall -Wno-unused-result -MMD -I. +LDFLAGS := + +# ############################ + +ifeq ($(strip $(wildcard $(DESTDIR)$(CONFIG_DIR)/$(TARGET).conf)),) + NEWCONF=$(TARGET).conf +else + NEWCONF=$(TARGET).conf.dist +endif + +# ############################ + +all: $(TARGET) + +$(TARGET): $(SRC:%.c=%.o) + @echo " Linking file: $@" + @$(CC) $(LDFLAGS) $^ -o $@ + +%.o: %.c + @echo " Building file: $<" + @$(CC) $(CFLAGS) -o $@ -c $< + +install: all + install -D -m 755 -s $(TARGET) $(DESTDIR)$(BINARY_DIR)/$(TARGET) + install -D -m 644 $(TARGET).conf $(DESTDIR)$(CONFIG_DIR)/$(NEWCONF) + sed -i -e "s:^logfile .*$$:logfile $(LOG_DIR)/$(TARGET).log:" \ + -e "s:^pidfile .*$$:pidfile $(PID_DIR)/$(TARGET).pid:" \ + $(DESTDIR)$(CONFIG_DIR)/$(NEWCONF) + install -d -m 755 $(DESTDIR)$(LOG_DIR) + +clean: + rm -rf *.d *.o $(TARGET) + +include $(shell find . -name \*.d 2> /dev/null) diff --git a/bcastfwd.c b/bcastfwd.c new file mode 100644 index 0000000..413bbbb --- /dev/null +++ b/bcastfwd.c @@ -0,0 +1,256 @@ +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "configfile.h" +#include "event.h" +#include "list.h" +#include "logging.h" +#include "sockaddr.h" + +struct host_entry { + struct list_head list; + struct sockaddr_in address; + int flags; +}; + +#define HOST_ACTIVE 0x0001 +#define HOST_LOCAL 0x0010 +#define HOST_REMOTE 0x0020 + +static LIST_HEAD(host_list); +static int raw_sock; +static int listen_sock; + +static struct event_fd *listen_event; +static struct event_timeout *local_timeout; +static struct event_timeout *remote_timeout; + +static int add_host_cb(const char *parameter, void *privdata) +{ + struct host_entry *entry = malloc(sizeof(struct host_entry)); + if (entry == NULL) { + log_print(LOG_WARN, "add_host_cb(): out of memory"); + return -1; + } + + if (parse_sockaddr(parameter, &entry->address)) { + log_print(LOG_WARN, "add_host_cb(): invalid host: '%s'", parameter); + free(entry); + return -1; + } + + entry->flags = (int)privdata | HOST_ACTIVE; + + log_print(LOG_INFO, "Forwarding to %s (%s)", get_sockaddr_buf(&entry->address), (entry->flags == HOST_LOCAL) ? "local" : "remote"); + list_add_tail(&entry->list, &host_list); + return 0; +} + +static int check_local_host_cb(void *privdata) +{ + struct host_entry *entry; + list_for_each_entry(entry, &host_list, list) { + if (!(entry->flags & HOST_LOCAL)) + continue; + + int sock = socket(AF_INET, SOCK_DGRAM, 0); + if (sock < 0) { + log_print(LOG_WARN, "check_local_host_cb(): socket()"); + continue; + } + + if (bind(sock,(struct sockaddr *)&entry->address, sizeof(struct sockaddr_in)) < 0) { + /* port in use, host seems to be active */ + if (!(entry->flags & HOST_ACTIVE)) { + log_print(LOG_INFO, "Host active: %s", get_sockaddr_buf(&entry->address)); + entry->flags |= HOST_ACTIVE; + } + + } else { + /* port not in use, host can not be active */ + if (entry->flags & HOST_ACTIVE) { + log_print(LOG_INFO, "Host not active: %s", get_sockaddr_buf(&entry->address)); + entry->flags &= ~(HOST_ACTIVE); + } + } + + close(sock); + } + + return 0; +} + +#if 0 +static int check_remote_host_cb(void *privdata) +{ + // TODO: don't know how to check + return 0; +} +#endif + +static int send_raw_udp(int fd, struct sockaddr_in *src, struct sockaddr_in *dst, const char *buf, int offset, int size) +{ + if (offset < sizeof(struct iphdr) + sizeof(struct udphdr)) + return -1; + + struct iphdr *ip = (struct iphdr *)buf; + struct udphdr *udp = (struct udphdr *)(buf + sizeof(struct iphdr)); + + ip->version = 4; /* IPv4 */ + ip->ihl = sizeof(struct iphdr) / sizeof(int); + ip->tos = 0; + ip->id = 0; + ip->frag_off = 0; + ip->ttl = 64; + ip->protocol = IPPROTO_UDP; + ip->check = 0; /* computed by kernel */ + ip->saddr = src->sin_addr.s_addr; + ip->daddr = dst->sin_addr.s_addr; + + int packetsize = sizeof(struct iphdr) + sizeof(struct udphdr) + size; + ip->tot_len = htons(packetsize); + + udp->source = src->sin_port; + udp->dest = dst->sin_port; + udp->check = 0; /* ignore */ + udp->len = htons(sizeof(struct udphdr) + size); + + if (sendto(fd, buf, packetsize, 0, (struct sockaddr *)dst, sizeof(struct sockaddr)) < 0) { + log_print(LOG_WARN, "send_raw_udp(): sendto()"); + return -1; + } + + return 0; +} + +static int listen_cb(int fd, void *privdata) +{ + struct sockaddr_in src_addr; + char buf[512]; + + socklen_t i = sizeof(src_addr); + int offset = sizeof(struct iphdr) + sizeof(struct udphdr); + + int len = recvfrom(fd, buf + offset, sizeof(buf) - offset, 0, (struct sockaddr *)&src_addr, &i); + if (len <= 0) { + log_print(LOG_WARN, "listen_cb(): recvfrom()"); + return -1; + } + + struct host_entry *entry; + list_for_each_entry(entry, &host_list, list) { + if (!(entry->flags & HOST_ACTIVE)) + continue; + + send_raw_udp(raw_sock, &src_addr, &entry->address, buf, offset, len); + } + + return 0; +} + +int bcast_close(void) +{ + if (remote_timeout != NULL) { + event_remove_timeout(remote_timeout); + remote_timeout = NULL; + } + + if (local_timeout != NULL) { + event_remove_timeout(local_timeout); + local_timeout = NULL; + } + + struct host_entry *entry, *tmp; + list_for_each_entry_safe(entry, tmp, &host_list, list) { + list_del(&entry->list); + free(entry); + } + + event_remove_fd(listen_event); + close(listen_sock); + close(raw_sock); + + return 0; +} + +int bcast_init(void) +{ + raw_sock = socket(PF_INET, SOCK_RAW, IPPROTO_UDP); + if (raw_sock < 0) { + log_print(LOG_ERROR, "bcast_init(): socket(raw)"); + return -1; + } + + int i = 1; + if (setsockopt(raw_sock, IPPROTO_IP, IP_HDRINCL, &i, sizeof(i)) < 0 ) { + log_print(LOG_ERROR, "bcast_init(): setsockopt(IP_HDRINCL)"); + close(raw_sock); + return -1; + } + + int listen_port = 0; + if (config_get_int("global", "listen", &listen_port, 0)) { + log_print(LOG_ERROR, "bcast_init(): Invalid listen port: %d", listen_port); + close(raw_sock); + return -1; + } + + struct sockaddr_in listen_addr; + listen_addr.sin_family = AF_INET, + listen_addr.sin_addr.s_addr = INADDR_ANY; + listen_addr.sin_port = htons(listen_port); + + listen_sock = socket(AF_INET, SOCK_DGRAM, 0); + if (listen_sock < 0) { + log_print(LOG_ERROR, "bcast_init(): socket(listen)"); + close(raw_sock); + return -1; + } + + if (bind(listen_sock, (struct sockaddr *)&listen_addr, sizeof(struct sockaddr_in)) < 0) { + log_print(LOG_ERROR, "bcast_init(): bind()"); + close(listen_sock); + close(raw_sock); + return -1; + } + + log_print(LOG_INFO, "Listening on %s", get_sockaddr_buf(&listen_addr)); + listen_event = event_add_readfd(NULL, listen_sock, listen_cb, NULL); + + int localcount, remotecount; + localcount = config_get_strings("local", "host", add_host_cb, (void *)HOST_LOCAL); + remotecount = config_get_strings("remote", "host", add_host_cb, (void *)HOST_REMOTE); + + if ((localcount + remotecount) == 0) { + log_print(LOG_ERROR, "bcast_init(): no hosts given"); + event_remove_fd(listen_event); + close(listen_sock); + close(raw_sock); + return -1; + } + + int check_interval = 0; + struct timeval tv; + tv.tv_usec = 0; + + if (localcount > 0) { + config_get_int("local", "check_interval", &check_interval, 10); + tv.tv_sec = check_interval; + local_timeout = event_add_timeout(&tv, check_local_host_cb, NULL); + } +#if 0 + if (remotecount > 0) { + config_get_int("remote", "check_interval", &check_interval, 60); + tv.tv_sec = check_interval; + remote_timeout = event_add_timeout(&tv, check_remote_host_cb, NULL); + } +#endif + return 0; +} diff --git a/bcastfwd.conf b/bcastfwd.conf new file mode 100644 index 0000000..2a57547 --- /dev/null +++ b/bcastfwd.conf @@ -0,0 +1,13 @@ +[global] +logfile bcastfwd.log +pidfile bcastfwd.pid + +listen 27015 + +[local] +check_interval 10 +host 10.10.250.133:27016 + +[remote] +check_interval 60 +host 10.10.250.134:27016 diff --git a/configfile.c b/configfile.c new file mode 100644 index 0000000..05a28b9 --- /dev/null +++ b/configfile.c @@ -0,0 +1,288 @@ +/*************************************************************************** + * Copyright (C) 03/2010 by Olaf Rempel * + * razzor@kopf-tisch.de * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; version 2 of the License * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#include +#include +#include +#include +#include + +#include "configfile.h" +#include "list.h" +#include "logging.h" + +#define BUFSIZE 1024 + +struct conf_section { + struct list_head list; + struct list_head tupel_list; + const char *name; +}; + +struct conf_tupel { + struct list_head list; + const char *option; + const char *parameter; +}; + +static LIST_HEAD(config_list); + +static struct conf_section * config_add_section(const char *name) +{ + struct conf_section *section; + section = malloc(sizeof(struct conf_section) + strlen(name)); + if (section == NULL) + return NULL; + + INIT_LIST_HEAD(§ion->list); + INIT_LIST_HEAD(§ion->tupel_list); + + section->name = strdup(name); + if (section->name == NULL) { + free(section); + return NULL; + } + + list_add_tail(§ion->list, &config_list); + return section; +} + +static int config_add_tupel(struct conf_section *section, const char *option, const char *parameter) +{ + struct conf_tupel *tupel = malloc(sizeof(struct conf_tupel)); + if (tupel == NULL) + return -1; + + INIT_LIST_HEAD(&tupel->list); + tupel->option = strdup(option); + tupel->parameter = strdup(parameter); + + if (tupel->option == NULL || tupel->parameter == NULL) { + free(tupel); + return -1; + } + + list_add_tail(&tupel->list, §ion->tupel_list); + return 0; +} + +int config_parse(const char *config) +{ + FILE *fz = fopen(config, "r"); + if (fz == NULL) { + log_print(LOG_ERROR, "config_parse(): %s", config); + return -1; + } + + char *line = malloc(BUFSIZE); + if (line == NULL) { + log_print(LOG_ERROR, "config_parse(): out of memory"); + fclose(fz); + return -1; + } + + int linenum = 0; + struct conf_section *section = NULL; + while (fgets(line, BUFSIZE, fz) != NULL) { + linenum++; + + if (line[0] == '#' || line[0] <= ' ') { + continue; + + } else if (line[0] == '[') { + char *tok = strtok(line +1, " ]\n"); + + if (tok == NULL || (section = config_add_section(tok)) == NULL) { + log_print(LOG_WARN, "config_parse(): invalid section in row %d", linenum); + free(line); + fclose(fz); + return -1; + } + continue; + + } else if (section == NULL) { + log_print(LOG_WARN, "config_parse(): missing section in row %d", linenum); + free(line); + fclose(fz); + return -1; + } + + char *tmp, *tok = strtok_r(line, " \t\n", &tmp); + if (tok != NULL) { + char *tok2; + while ((tok2 = strtok_r(NULL, " \n", &tmp))) { + if (config_add_tupel(section, tok, tok2) != 0) + log_print(LOG_WARN, "config_parse(): invalid row %d", linenum); + } + } + } + + fclose(fz); + free(line); + return 0; +} + +void config_free(void) +{ + struct conf_section *section, *section_tmp; + struct conf_tupel *tupel, *tupel_tmp; + + list_for_each_entry_safe(section, section_tmp, &config_list, list) { + list_for_each_entry_safe(tupel, tupel_tmp, §ion->tupel_list, list) { + list_del(&tupel->list); + free((char *)tupel->option); + free((char *)tupel->parameter); + free(tupel); + } + list_del(§ion->list); + free(section); + } +} + +static struct conf_section * config_get_section(const char *name) +{ + struct conf_section *section; + + list_for_each_entry(section, &config_list, list) { + if (!strcmp(section->name, name)) + return section; + } + return NULL; +} + +const char * config_get_string(const char *section_str, const char *option, const char *def) +{ + struct conf_section *section = config_get_section(section_str); + if (section != NULL) { + struct conf_tupel *tupel; + list_for_each_entry(tupel, §ion->tupel_list, list) { + if (!strcmp(tupel->option, option)) + return tupel->parameter; + } + } + + if (def != NULL) + log_print(LOG_WARN, "config [%s:%s] not found, using default: '%s'", + section_str, option, def); + return def; +} + +int config_get_int(const char *section, const char *option, int *value, int def) +{ + const char *ret = config_get_string(section, option, NULL); + if (ret == NULL) { + log_print(LOG_WARN, "config [%s:%s] not found, using default: '%d'", + section, option, def); + + *value = def; + return -1; + } + + char *tmp; + *value = strtol(ret, &tmp, 0); + + if (*tmp != '\0' && !isspace(*tmp)) { + log_print(LOG_WARN, "config [%s:%s] not an integer: '%s', using default '%d'", + section, option, ret, def); + + *value = def; + return -1; + } + + return 0; +} + +int config_get_strings(const char *section_str, const char *option, + int (*callback)(const char *value, void *privdata), + void *privdata) +{ + struct conf_section *section = config_get_section(section_str); + if (section == NULL) + return -1; + + int cnt = 0; + struct conf_tupel *tupel; + list_for_each_entry(tupel, §ion->tupel_list, list) { + if (!strcmp(tupel->option, option)) + if (callback(tupel->parameter, privdata) == 0) + cnt++; + } + return cnt; +} + +struct strtoken * strtokenize(const char *input, const char *delim, int maxfields) +{ + struct strtoken *tokens = malloc(sizeof(struct strtoken) + + (maxfields +1) * sizeof(char *) + + strlen(input)); + if (tokens == NULL) + return NULL; + + char *ptr = (char *)&tokens->field[maxfields]; + strcpy(ptr, input); + + int i; + char *tmp; + + tokens->count = 0; + for (i = 0; i < maxfields; i++) { + tokens->field[i] = strtok_r(ptr, delim, &tmp); + ptr = NULL; + + if (tokens->field[i] != NULL) + tokens->count++; + } + + return tokens; +} + +struct strtoken * config_get_strtoken(const char *section, const char *option, const char *delim, int maxfields) +{ + const char *ret = config_get_string(section, option, NULL); + if (ret == NULL) { + log_print(LOG_WARN, "config [%s:%s] not found", section, option); + + return NULL; + } + + return strtokenize(ret, delim, maxfields); +} + +int config_get_strtokens(const char *section_str, const char *option, const char *delim, int maxfields, + int (*callback)(struct strtoken *data, void *privdata), + void *privdata) +{ + struct conf_section *section = config_get_section(section_str); + if (section == NULL) + return -1; + + int cnt = 0; + struct conf_tupel *tupel; + list_for_each_entry(tupel, §ion->tupel_list, list) { + if (!strcmp(tupel->option, option)) { + struct strtoken *tokens = strtokenize(tupel->parameter, delim, maxfields); + if (tokens != NULL) { + if (callback(tokens, privdata) == 0) + cnt++; + + free(tokens); + } + } + } + return cnt; +} diff --git a/configfile.h b/configfile.h new file mode 100644 index 0000000..63c2edb --- /dev/null +++ b/configfile.h @@ -0,0 +1,28 @@ +#ifndef _CONFIG_H_ +#define _CONFIG_H_ + +int config_parse(const char *config); +void config_free(void); + +const char * config_get_string(const char *section_str, const char *option, const char *def); + +int config_get_int(const char *section, const char *option, int *value, int def); + +int config_get_strings(const char *section_str, const char *option, + int (*callback)(const char *value, void *privdata), + void *privdata); + +struct strtoken { + int count; + char *field[0]; +}; + +struct strtoken * strtokenize(const char *input, const char *delim, int maxfields); + +struct strtoken * config_get_strtoken(const char *section_str, const char *option, const char *delim, int maxfields); + +int config_get_strtokens(const char *section_str, const char *option, const char *delim, int maxfields, + int (*callback)(struct strtoken *tokens, void *privdata), + void *privdata); + +#endif /* _CONFIG_H_ */ diff --git a/event.c b/event.c new file mode 100644 index 0000000..b5a3898 --- /dev/null +++ b/event.c @@ -0,0 +1,310 @@ +/*************************************************************************** + * Copyright (C) 03/2010 by Olaf Rempel * + * razzor@kopf-tisch.de * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; version 2 of the License * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#include +#include +#include + +#include +#include +#include + +#include "list.h" +#include "logging.h" + +#include "event.h" + +static LIST_HEAD(event_fd_list); +static LIST_HEAD(event_timeout_list); + +struct event_fd { + struct list_head list; + unsigned int flags; + int fd; + int (*read_cb)(int fd, void *privdata); + int (*write_cb)(int fd, void *privdata); + void *read_priv; + void *write_priv; +}; + +struct event_timeout { + struct list_head list; + unsigned int flags; + struct timeval intervall; + struct timeval nextrun; + int (*callback)(void *privdata); + void *privdata; +}; + +struct event_fd * event_add_fd( + struct event_fd *entry, + int fd, + int type, + int (*callback)(int fd, void *privdata), + void *privdata) +{ + /* check valid filediskriptor */ + if (fd < 0 || fd > FD_SETSIZE) { + log_print(LOG_ERROR, "event_add_fd(): invalid fd"); + return NULL; + } + + /* check valid type (read/write) */ + if (!(type & FD_TYPES)) { + log_print(LOG_ERROR, "event_add_fd(): invalid type"); + return NULL; + } + + /* create new entry */ + if (entry == NULL) { + entry = malloc(sizeof(struct event_fd)); + if (entry == NULL) { + log_print(LOG_ERROR, "event_add_fd(): out of memory"); + return NULL; + } + + memset(entry, 0, sizeof(struct event_fd)); + entry->flags |= EVENT_NEW; + entry->fd = fd; + + /* put it on the list */ + list_add_tail(&entry->list, &event_fd_list); + } + + if (type & FD_READ) { + entry->flags = (callback != NULL) ? (entry->flags | FD_READ | EVENT_NEW) : (entry->flags & ~FD_READ); + entry->read_cb = callback; + entry->read_priv = privdata; + + } else if (type & FD_WRITE) { + entry->flags = (callback != NULL) ? (entry->flags | FD_WRITE | EVENT_NEW) : (entry->flags & ~FD_WRITE); + entry->write_cb = callback; + entry->write_priv = privdata; + } + + return entry; +} + +int event_get_fd(struct event_fd *entry) +{ + return (entry != NULL) ? entry->fd: -1; +} + +void event_remove_fd(struct event_fd *entry) +{ + /* mark the event as deleted -> remove in select() loop */ + entry->flags |= EVENT_DELETE; +} + +static void add_timeval(struct timeval *ret, struct timeval *a, struct timeval *b) +{ + ret->tv_usec = a->tv_usec + b->tv_usec; + ret->tv_sec = a->tv_sec + b->tv_sec; + + if (ret->tv_usec >= 1000000) { + ret->tv_usec -= 1000000; + ret->tv_sec++; + } +} + +static void sub_timeval(struct timeval *ret, struct timeval *a, struct timeval *b) +{ + ret->tv_usec = a->tv_usec - b->tv_usec; + ret->tv_sec = a->tv_sec - b->tv_sec; + + if (ret->tv_usec < 0) { + ret->tv_usec += 1000000; + ret->tv_sec--; + } +} + +static int cmp_timeval(struct timeval *a, struct timeval *b) +{ + if (a->tv_sec > b->tv_sec) + return -1; + + if (a->tv_sec < b->tv_sec) + return 1; + + if (a->tv_usec > b->tv_usec) + return -1; + + if (a->tv_usec < b->tv_usec) + return 1; + + return 0; +} + +static void schedule_nextrun(struct event_timeout *entry, struct timeval *now) +{ + add_timeval(&entry->nextrun, now, &entry->intervall); + + struct event_timeout *search; + list_for_each_entry(search, &event_timeout_list, list) { + if (search->nextrun.tv_sec > entry->nextrun.tv_sec) { + list_add_tail(&entry->list, &search->list); + return; + + } else if (search->nextrun.tv_sec == entry->nextrun.tv_sec && + search->nextrun.tv_usec > entry->nextrun.tv_usec) { + list_add_tail(&entry->list, &search->list); + return; + } + } + list_add_tail(&entry->list, &event_timeout_list); +} + +struct event_timeout * event_add_timeout( + struct timeval *timeout, + int (*callback)(void *privdata), + void *privdata) +{ + struct event_timeout *entry; + entry = malloc(sizeof(struct event_timeout)); + if (entry == NULL) { + log_print(LOG_ERROR, "event_add_timeout(): out of memory"); + return NULL; + } + + entry->flags = 0; + memcpy(&entry->intervall, timeout, sizeof(entry->intervall)); + entry->callback = callback; + entry->privdata = privdata; + + struct timeval now; + gettimeofday(&now, NULL); + + schedule_nextrun(entry, &now); + return entry; +} + +void event_remove_timeout(struct event_timeout *entry) +{ + /* mark the event as deleted -> remove in select() loop */ + entry->flags |= EVENT_DELETE; +} + +int event_loop(int (*pre_select_cb)(int *maxfd, void *readfds, void *writefds, struct timeval *timeout, void *privdata), + int (*post_select_cb)(int retval, void *readfds, void *writefds, void *privdata), + void *privdata) +{ + while (1) { + /* default value if no application timeout is present */ + struct timeval timeout = { + .tv_sec = 3600, + .tv_usec = 0, + }; + + if (!list_empty(&event_timeout_list)) { + struct timeval now; + gettimeofday(&now, NULL); + + struct event_timeout *entry, *tmp; + list_for_each_entry_safe(entry, tmp, &event_timeout_list, list) { + if (entry->flags & EVENT_DELETE) { + list_del(&entry->list); + free(entry); + continue; + } + + /* first timeout not elapsed, exit search (since list is sorted) */ + if (cmp_timeval(&entry->nextrun, &now) == -1) + break; + + /* remove event from list */ + list_del(&entry->list); + + /* execute callback, when callback returns 0 -> schedule event again */ + if (entry->callback(entry->privdata)) { + free(entry); + + } else { + schedule_nextrun(entry, &now); + } + } + + if (!list_empty(&event_timeout_list)) { + entry = list_entry(event_timeout_list.next, typeof(*entry), list); + + /* calc select() timeout */ + sub_timeval(&timeout, &entry->nextrun, &now); + } + } + + struct event_fd *entry, *tmp; + int maxfd = -1; + + fd_set readfds, writefds; + FD_ZERO(&readfds); + FD_ZERO(&writefds); + + list_for_each_entry_safe(entry, tmp, &event_fd_list, list) { + entry->flags &= ~EVENT_NEW; + + if (entry->flags & EVENT_DELETE) { + list_del(&entry->list); + free(entry); + continue; + } + + if (entry->flags & FD_READ) + FD_SET(entry->fd, &readfds); + + if (entry->flags & FD_WRITE) + FD_SET(entry->fd, &writefds); + + maxfd = (entry->fd > maxfd) ? entry->fd : maxfd; + } + + maxfd++; + + /* exit loop if callback returns true */ + if (pre_select_cb != NULL && pre_select_cb(&maxfd, (void *)&readfds, (void *)&writefds, &timeout, privdata) != 0) + break; + + int retval = select(maxfd, &readfds, &writefds, NULL, &timeout); + if (retval < 0 && errno == EINTR) { + errno = 0; + continue; + + } else if (retval < 0) { + log_print(LOG_ERROR, "event_loop(): select():"); + continue; + } + + /* exit loop if callback returns true */ + if (post_select_cb != NULL && post_select_cb(retval, (void *)&readfds, (void *)&writefds, privdata) != 0) + break; + + /* timeout */ + if (retval == 0) + continue; + + list_for_each_entry(entry, &event_fd_list, list) { + if (((entry->flags & (FD_READ | EVENT_NEW)) == FD_READ) && FD_ISSET(entry->fd, &readfds)) + if (entry->read_cb(entry->fd, entry->read_priv) != 0) + entry->flags |= EVENT_DELETE; + + if (((entry->flags & (FD_WRITE | EVENT_NEW)) == FD_WRITE) && FD_ISSET(entry->fd, &writefds)) + if (entry->write_cb(entry->fd, entry->write_priv) != 0) + entry->flags |= EVENT_DELETE; + } + } + + return 0; +} diff --git a/event.h b/event.h new file mode 100644 index 0000000..da3391e --- /dev/null +++ b/event.h @@ -0,0 +1,44 @@ +#ifndef _EVENT_H_ +#define _EVENT_H_ + +#include + +#define EVENT_NEW 0x1000 +#define EVENT_DELETE 0x2000 + +#define FD_READ 0x0001 +#define FD_WRITE 0x0002 +#define FD_TYPES (FD_READ | FD_WRITE) + +#define event_add_readfd(entry, fd, callback, privdata) \ + event_add_fd(entry, fd, FD_READ, callback, privdata) + +#define event_add_writefd(entry, fd, callback, privdata) \ + event_add_fd(entry, fd, FD_WRITE, callback, privdata) + +/* inner details are not visible to external users (TODO: size unknown) */ +struct event_fd; +struct event_timeout; + +struct event_fd * event_add_fd( + struct event_fd *entry, + int fd, + int type, + int (*callback)(int fd, void *privdata), + void *privdata); + +int event_get_fd(struct event_fd *entry); +void event_remove_fd(struct event_fd *entry); + +struct event_timeout * event_add_timeout( + struct timeval *timeout, + int (*callback)(void *privdata), + void *privdata); + +void event_remove_timeout(struct event_timeout *entry); + +int event_loop(int (*pre_select_cb)(int *maxfd, void *readfds, void *writefds, struct timeval *timeout, void *privdata), + int (*post_select_cb)(int retval, void *readfds, void *writefds, void *privdata), + void *privdata); + +#endif /* _EVENT_H_ */ diff --git a/list.h b/list.h new file mode 100644 index 0000000..61f8d93 --- /dev/null +++ b/list.h @@ -0,0 +1,268 @@ +#ifndef _LIST_H_ +#define _LIST_H_ + +/* + * stolen from linux kernel 2.6.11 (http://kernel.org/) + * linux/include/linux/stddef.h (offsetoff) + * linux/include/linux/kernel.h (container_of) + * linux/include/linux/list.h (*list*) + * linux/include/linux/netfilter_ipv4/listhelp.h (LIST_FIND) + * + * modified by Olaf Rempel + */ +#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER) + +#define container_of(ptr, type, member) ({ \ + const typeof( ((type *)0)->member ) *__mptr = (ptr); \ + (type *)( (char *)__mptr - offsetof(type,member) );}) + +struct list_head { + struct list_head *next, *prev; +}; + +#define LIST_HEAD_INIT(name) { &(name), &(name) } + +#define LIST_HEAD(name) \ + struct list_head name = LIST_HEAD_INIT(name) + +#define INIT_LIST_HEAD(ptr) do { \ + (ptr)->next = (ptr); (ptr)->prev = (ptr); \ +} while (0) + +/* + * Insert a new entry between two known consecutive entries. + * + * This is only for internal list manipulation where we know + * the prev/next entries already! + */ +static inline void __list_add(struct list_head *new, + struct list_head *prev, + struct list_head *next) +{ + next->prev = new; + new->next = next; + new->prev = prev; + prev->next = new; +} + +/* + * list_add - add a new entry + * @new: new entry to be added + * @head: list head to add it after + * + * Insert a new entry after the specified head. + * This is good for implementing stacks. + */ +static inline void list_add(struct list_head *new, struct list_head *head) +{ + __list_add(new, head, head->next); +} + +/* + * list_add_tail - add a new entry + * @new: new entry to be added + * @head: list head to add it before + * + * Insert a new entry before the specified head. + * This is useful for implementing queues. + */ +static inline void list_add_tail(struct list_head *new, struct list_head *head) +{ + __list_add(new, head->prev, head); +} + +/* + * Delete a list entry by making the prev/next entries + * point to each other. + * + * This is only for internal list manipulation where we know + * the prev/next entries already! + */ +static inline void __list_del(struct list_head * prev, struct list_head * next) +{ + next->prev = prev; + prev->next = next; +} + +/* + * list_del - deletes entry from list. + * @entry: the element to delete from the list. + * Note: list_empty on entry does not return true after this, the entry is + * in an undefined state. + */ +static inline void list_del(struct list_head *entry) +{ + __list_del(entry->prev, entry->next); + entry->next = NULL; + entry->prev = NULL; +} + +/* + * list_del_init - deletes entry from list and reinitialize it. + * entry: the element to delete from the list. + */ +static inline void list_del_init(struct list_head *entry) +{ + __list_del(entry->prev, entry->next); + INIT_LIST_HEAD(entry); +} + +/* + * list_move - delete from one list and add as another's head + * @list: the entry to move + * @head: the head that will precede our entry + */ +static inline void list_move(struct list_head *list, struct list_head *head) +{ + __list_del(list->prev, list->next); + list_add(list, head); +} + +/* + * list_move_tail - delete from one list and add as another's tail + * @list: the entry to move + * @head: the head that will follow our entry + */ +static inline void list_move_tail(struct list_head *list, + struct list_head *head) +{ + __list_del(list->prev, list->next); + list_add_tail(list, head); +} + +/* + * list_empty - tests whether a list is empty + * @head: the list to test. + */ +static inline int list_empty(const struct list_head *head) +{ + return head->next == head; +} + +static inline void __list_splice(struct list_head *list, + struct list_head *head) +{ + struct list_head *first = list->next; + struct list_head *last = list->prev; + struct list_head *at = head->next; + + first->prev = head; + head->next = first; + + last->next = at; + at->prev = last; +} + +/* + * list_splice - join two lists + * @list: the new list to add. + * @head: the place to add it in the first list. + */ +static inline void list_splice(struct list_head *list, struct list_head *head) +{ + if (!list_empty(list)) + __list_splice(list, head); +} + +/* + * list_splice_init - join two lists and reinitialise the emptied list. + * @list: the new list to add. + * @head: the place to add it in the first list. + * + * The list at @list is reinitialised + */ +static inline void list_splice_init(struct list_head *list, + struct list_head *head) +{ + if (!list_empty(list)) { + __list_splice(list, head); + INIT_LIST_HEAD(list); + } +} + +/* + * list_entry - get the struct for this entry + * @ptr: the &struct list_head pointer. + * @type: the type of the struct this is embedded in. + * @member: the name of the list_struct within the struct. + */ +#define list_entry(ptr, type, member) \ + container_of(ptr, type, member) + +/* + * list_for_each - iterate over a list + * @pos: the &struct list_head to use as a loop counter. + * @head: the head for your list. + */ +#define list_for_each(pos, head) \ + for (pos = (head)->next; pos != (head); pos = pos->next) + +/* + * list_for_each_prev - iterate over a list backwards + * @pos: the &struct list_head to use as a loop counter. + * @head: the head for your list. + */ +#define list_for_each_prev(pos, head) \ + for (pos = (head)->prev; pos != (head); pos = pos->prev) + +/* + * list_for_each_safe - iterate over a list safe against removal of list entry + * @pos: the &struct list_head to use as a loop counter. + * @n: another &struct list_head to use as temporary storage + * @head: the head for your list. + */ +#define list_for_each_safe(pos, n, head) \ + for (pos = (head)->next, n = pos->next; pos != (head); \ + pos = n, n = pos->next) + +/* + * list_for_each_entry - iterate over list of given type + * @pos: the type * to use as a loop counter. + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + */ +#define list_for_each_entry(pos, head, member) \ + for (pos = list_entry((head)->next, typeof(*pos), member); \ + &pos->member != (head); \ + pos = list_entry(pos->member.next, typeof(*pos), member)) + +/* + * list_for_each_entry_reverse - iterate backwards over list of given type. + * @pos: the type * to use as a loop counter. + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + */ +#define list_for_each_entry_reverse(pos, head, member) \ + for (pos = list_entry((head)->prev, typeof(*pos), member); \ + &pos->member != (head); \ + pos = list_entry(pos->member.prev, typeof(*pos), member)) + +/* + * list_for_each_entry_safe - iterate over list of given type safe against removal of list entry + * @pos: the type * to use as a loop counter. + * @n: another type * to use as temporary storage + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + */ +#define list_for_each_entry_safe(pos, n, head, member) \ + for (pos = list_entry((head)->next, typeof(*pos), member), \ + n = list_entry(pos->member.next, typeof(*pos), member); \ + &pos->member != (head); \ + pos = n, n = list_entry(n->member.next, typeof(*n), member)) + + +/* Return pointer to first true entry, if any, or NULL. A macro + required to allow inlining of cmpfn. */ +#define LIST_FIND(head, cmpfn, type, args...) \ +({ \ + const struct list_head *__i, *__j = NULL; \ + \ + list_for_each(__i, (head)) \ + if (cmpfn((const type)__i , ## args)) { \ + __j = __i; \ + break; \ + } \ + (type)__j; \ +}) + +#endif /* _LIST_H_ */ diff --git a/logging.c b/logging.c new file mode 100644 index 0000000..ddc1128 --- /dev/null +++ b/logging.c @@ -0,0 +1,114 @@ +/*************************************************************************** + * Copyright (C) 07/2007 by Olaf Rempel * + * razzor@kopf-tisch.de * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; version 2 of the License * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#include +#include +#include + +#include +#include +#include +#include + +#include "logging.h" + +#define BUFSIZE 8192 + +static FILE *log_fd = NULL; +static int log_prio = LOG_EVERYTIME; +static char *buffer = NULL; + +int log_print(int prio, const char *fmt, ...) +{ + va_list az; + int len = 0, retval; + + if (prio < log_prio) + return 0; + + if (buffer == NULL) { + buffer = malloc(BUFSIZE); + if (buffer == NULL) { + fprintf(stderr, "log_print(): out of memory\n"); + return -1; + } + } + + if (log_fd != NULL) { + time_t tzgr; + time(&tzgr); + + len += strftime(buffer, BUFSIZE, "%b %d %H:%M:%S :", localtime(&tzgr)); + } + + va_start(az, fmt); + len += vsnprintf(buffer + len, BUFSIZE - len, fmt, az); + va_end(az); + + if (len < 0 || len >= BUFSIZE) { + errno = 0; + return log_print(LOG_ERROR, "log_print: arguments too long"); + } + + if (errno) { + len += snprintf(buffer + len, BUFSIZE - len, ": %s", strerror(errno)); + errno = 0; + } + + retval = fprintf((log_fd ? log_fd : stderr), "%s\n", buffer); + fflush(log_fd); + return retval; +} + +void log_close(void) +{ + if (buffer) { + free(buffer); + buffer = NULL; + } + + if (log_fd) { + fclose(log_fd); + log_fd = NULL; + } +} + +int log_init(const char *logfile) +{ + if (log_fd != NULL) + log_close(); + + log_fd = fopen(logfile, "a"); + if (log_fd == NULL) { + fprintf(stderr, "log_init(): can not open logfile"); + return -1; + } + + if (fcntl(fileno(log_fd), F_SETFD, FD_CLOEXEC) < 0) { + fprintf(stderr, "log_init(): fcntl(FD_CLOEXEC)"); + return -1; + } + + log_prio = LOG_EVERYTIME; + return 0; +} + +void log_setprio(int prio) +{ + log_prio = prio; +} diff --git a/logging.h b/logging.h new file mode 100644 index 0000000..87f1c2e --- /dev/null +++ b/logging.h @@ -0,0 +1,23 @@ +#ifndef _LOGGING_H_ +#define _LOGGING_H_ + +#define LOG_EMERG 0 /* system is unusable */ +#define LOG_ALERT 1 /* action must be taken immediately */ +#define LOG_CRIT 2 /* critical conditions */ +#define LOG_ERR 3 /* error conditions */ +#define LOG_WARNING 4 /* warning conditions */ +#define LOG_NOTICE 5 /* normal but significant condition */ +#define LOG_INFO 6 /* informational */ +#define LOG_DEBUG 7 /* debug-level messages */ + +#define LOG_EVERYTIME LOG_EMERG +#define LOG_ERROR LOG_ERR +#define LOG_WARN LOG_WARNING + +int log_init(const char *logfile); +void log_close(void); +void log_setprio(int prio); + +int log_print(int prio, const char *fmt, ... ); + +#endif /* _LOGGING_H_ */ diff --git a/main.c b/main.c new file mode 100644 index 0000000..2e78233 --- /dev/null +++ b/main.c @@ -0,0 +1,162 @@ +/*************************************************************************** + * Copyright (C) 12/2010 by Olaf Rempel * + * razzor@kopf-tisch.de * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "configfile.h" +#include "event.h" +#include "logging.h" +#include "pidfile.h" +#include "signals.h" + +#define DEFAULT_CONFIG "bcastfwd.conf" +#define DEFAULT_LOGFILE "bcastfwd.log" +#define DEFAULT_PIDFILE "bcastfwd.pid" + +int bcast_init(void); +int bcast_close(void); + +static struct option opts[] = { + {"config", 1, 0, 'c'}, + {"debug", 0, 0, 'd'}, + {"help", 0, 0, 'h'}, + {0, 0, 0, 0} +}; + +static int restart_var; + +static void trigger_restart(void *privdata) +{ + int *restart = (int *)privdata; + *restart = 1; +} + +int check_restart(int *maxfd, void *readfds, void *writefds, struct timeval *timeout, void *privdata) +{ + int *restart = (int *)privdata; + if (*restart == 1) { + *restart = 0; + return 1; + } + + return 0; +} + +int main(int argc, char *argv[]) +{ + char *config = DEFAULT_CONFIG; + int code, arg = 0, debug = 0; + + do { + code = getopt_long(argc, argv, "c:dh", opts, &arg); + + switch (code) { + case 'c': /* config */ + config = optarg; + break; + + case 'd': /* debug */ + debug = 1; + break; + + case 'h': /* help */ + printf("Usage: bcastfwd [options]\n" + "Options: \n" + " --config -c configfile use this configfile\n" + " --debug -d do not fork and log to stderr\n" + " --help -h this help\n" + "\n"); + exit(0); + break; + + case '?': /* error */ + exit(1); + break; + + default: /* unknown / all options parsed */ + break; + } + } while (code != -1); + + /* parse config file */ + if (config_parse(config) < 0) + exit(1); + + if (!debug) { + /* check pidfile */ + const char *pidfile = config_get_string("global", "pidfile", DEFAULT_PIDFILE); + if (pidfile_check(pidfile, 1) != 0) { + log_print(LOG_ERROR, "bcastfwd already running"); + exit(1); + } + + /* start logging */ + const char *logfile = config_get_string("global", "logfile", DEFAULT_LOGFILE); + if (log_init(logfile) < 0) + exit(1); + + /* zum daemon mutieren */ + if (daemon(-1, 0) < 0) { + log_print(LOG_ERROR, "failed to daemonize"); + exit(1); + } + + /* create pidfile */ + if (pidfile_create(pidfile) < 0) { + log_print(LOG_ERROR, "failed to create pidfile %s", pidfile); + exit(1); + } + } + + signal_init(); + signal_add_callback(SIGHUP, trigger_restart, (void *)&restart_var); + + log_print(LOG_EVERYTIME, "bcastfwd started (pid:%d)", getpid()); + + while (1) { + if (bcast_init()) + break; + + /* exited on restart / SIGUSR1 */ + event_loop(check_restart, NULL, (void *)&restart_var); + + bcast_close(); + + config_free(); + + if (config_parse(config) < 0) + break; + + const char *logfile = config_get_string("global", "logfile", DEFAULT_LOGFILE); + if (!debug && log_init(logfile) < 0) + break; + + log_print(LOG_EVERYTIME, "bcastfwd restarted (pid:%d) ", getpid()); + } + + return 0; +} diff --git a/pidfile.c b/pidfile.c new file mode 100644 index 0000000..f5dd0be --- /dev/null +++ b/pidfile.c @@ -0,0 +1,73 @@ +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "logging.h" + +int pidfile_create(const char *filename) +{ + int fd = open(filename, O_CREAT | O_EXCL | O_RDWR, 0644); + if (fd < 0) + return -1; + + char buf[8]; + int len = snprintf(buf, sizeof(buf), "%d", getpid()); + write(fd, buf, len); + + close(fd); + return 0; +} + +int pidfile_remove(const char *filename) +{ + return unlink(filename); +} + +pid_t pidfile_check(const char *filename, int remove_stale) +{ + int fd = open(filename, O_RDWR); + if (fd < 0) { + if (errno == ENOENT) { + errno = 0; + return 0; + } + return -1; + } + + char buf[9]; + int len = read(fd, buf, sizeof(buf) -1); + buf[len] = '\0'; + + close(fd); + + char *tmp; + pid_t pid = strtol(buf, &tmp, 10); + if (len == 0 || tmp == buf) + pid = -1; + + /* just return the pid */ + if (!remove_stale) + return pid; + + /* invalid pid, remove stale file */ + if (pid == -1) { + pidfile_remove(filename); + return 0; + } + + /* check if pid is still running */ + if (kill(pid, 0) == 0 || errno != ESRCH) { + errno = 0; + return pid; + } + + errno = 0; + pidfile_remove(filename); + return 0; +} diff --git a/pidfile.h b/pidfile.h new file mode 100644 index 0000000..2ba1512 --- /dev/null +++ b/pidfile.h @@ -0,0 +1,11 @@ +#ifndef _PIDFILE_H_ +#define _PIDFILE_H_ + +#include + +int pidfile_create(const char *filename); +int pidfile_remove(const char *filename); + +pid_t pidfile_check(const char *filename, int remove_stale); + +#endif // _PIDFILE_H_ diff --git a/signals.c b/signals.c new file mode 100644 index 0000000..4ae7176 --- /dev/null +++ b/signals.c @@ -0,0 +1,151 @@ +/*************************************************************************** + * Copyright (C) 05/2009 by Olaf Rempel * + * razzor@kopf-tisch.de * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; version 2 of the License * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#include +#include + +#include +#include + +#include "event.h" +#include "list.h" +#include "logging.h" +#include "signals.h" + +struct signal_entry { + struct list_head list; + int signum; + int deleted; + + void (*callback)(void *privdata); + void *privdata; +}; + +static LIST_HEAD(callback_list); +static int sig_pipe[2]; + +static void sig_handler(int signal) +{ + unsigned char signum = (signal & 0xFF); + write(sig_pipe[1], &signum, 1); +} + +int signal_remove_callback(int signum, int type) +{ + /* mark all old handler for deletion */ + struct signal_entry *search; + list_for_each_entry(search, &callback_list, list) { + if (search->signum == signum) + search->deleted = 1; + } + + struct sigaction sig_action; + + switch (type) { + case SIG_IGNORE: + sig_action.sa_handler = SIG_IGN; + break; + + default: + case SIG_DEFAULT: + sig_action.sa_handler = SIG_DFL; + break; + } + + if (sigaction(signum, &sig_action, NULL) < 0) { + log_print(LOG_WARN, "signal_remove_callback(): sigaction()"); + return -1; + } + + /* trigger remove */ + sig_handler(0); + return 0; +} + +int signal_add_callback(int signum, void (*callback)(void *privdata), void *privdata) +{ + struct signal_entry *entry = malloc(sizeof(struct signal_entry)); + if (entry == NULL) { + log_print(LOG_WARN, "signal_add_callback(): out of memory"); + return -1; + } + + entry->signum = signum; + entry->deleted = 0; + entry->callback = callback; + entry->privdata = privdata; + list_add_tail(&entry->list, &callback_list); + + struct sigaction sig_action = { + .sa_handler = sig_handler, + }; + + if (sigaction(signum, &sig_action, NULL) < 0) { + log_print(LOG_WARN, "signal_add_callback(): sigaction()"); + list_del(&entry->list); + free(entry); + return -1; + } + + return 0; +} + +static int sig_event(int fd, void *privdata) +{ + unsigned char signum; + int len = read(fd, &signum, 1); + if (len <= 0) { + log_print(LOG_WARN, "sig_event(): read()"); + return -1; + } + + struct signal_entry *search, *tmp; + list_for_each_entry_safe(search, tmp, &callback_list, list) { + if (search->deleted) { + list_del(&search->list); + free(search); + continue; + } + + if (search->signum == signum) + search->callback(search->privdata); + } + + return 0; +} + +int signal_init(void) +{ + if (pipe(sig_pipe) < 0) { + log_print(LOG_ERROR, "signal_init(): pipe()"); + return -1; + } + + if (fcntl(sig_pipe[0], F_SETFD, FD_CLOEXEC) < 0) { + log_print(LOG_WARN, "signal_init(): fcntl(FD_CLOEXEC)"); + return -1; + } + + if (fcntl(sig_pipe[1], F_SETFD, FD_CLOEXEC) < 0) { + log_print(LOG_WARN, "signal_init(): fcntl(FD_CLOEXEC)"); + return -1; + } + + event_add_readfd(NULL, sig_pipe[0], sig_event, NULL); + return 0; +} diff --git a/signals.h b/signals.h new file mode 100644 index 0000000..3a9cc16 --- /dev/null +++ b/signals.h @@ -0,0 +1,12 @@ +#ifndef _SIGNALS_H +#define _SIGNALS_H + +#define SIG_DEFAULT 0x00 +#define SIG_IGNORE 0x01 + +int signal_remove_callback(int signum, int type); +int signal_add_callback(int signum, void (*callback)(void *privdata), void *privdata); + +int signal_init(void); + +#endif // _SIGNALS_H diff --git a/sockaddr.c b/sockaddr.c new file mode 100644 index 0000000..0559097 --- /dev/null +++ b/sockaddr.c @@ -0,0 +1,117 @@ +/*************************************************************************** + * Copyright (C) 07/2007 by Olaf Rempel * + * razzor@kopf-tisch.de * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; version 2 of the License * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#include +#include +#include + +#include +#include +#include +#include + +int parse_sockaddr(const char *addr, struct sockaddr_in *sa) +{ + char *buf = strdup(addr); + if (buf == NULL) + return -1; + + char *tmp; + char *ipstr = strtok_r(buf, ":", &tmp); + if (ipstr == NULL) { + free(buf); + return -2; + } + + sa->sin_family = AF_INET; + if (inet_pton(AF_INET, ipstr, &sa->sin_addr) <= 0) { + free(buf); + return -3; + } + + char *portstr = strtok_r(NULL, " \r\n", &tmp); + if (portstr == NULL) { + free(buf); + return -4; + } + + int port = atoi(portstr); + if (port < 0 || port > 65535) { + free(buf); + return -5; + } + + sa->sin_port = htons(port); + free(buf); + return 0; +} + +int parse_subnet(const char *addr, struct in_addr *net, struct in_addr *mask) +{ + char *buf = strdup(addr); + if (buf == NULL) + return -1; + + char *tmp; + char *netstr = strtok_r(buf, "/", &tmp); + if (netstr == NULL) { + free(buf); + return -2; + } + + if (inet_pton(AF_INET, netstr, net) <= 0) { + free(buf); + return -3; + } + + char *maskstr = strtok_r(NULL, " \r\n", &tmp); + if (maskstr == NULL) { + mask->s_addr = ~0; + + } else if (inet_pton(AF_INET, maskstr, mask) <= 0) { + int maskbits = atoi(maskstr); + if (maskbits < 0 || maskbits > 32) { + free(buf); + return -4; + } + + mask->s_addr = htonl(~0 << (32 - maskbits)); + } + + free(buf); + return 0; +} + +int get_sockaddr(char *buf, int size, struct sockaddr_in *addr) +{ + return snprintf(buf, size, "%s:%d", inet_ntoa(addr->sin_addr), ntohs(addr->sin_port)); +} + +char * get_sockaddr_buf(struct sockaddr_in *addr) +{ + static char ret[24]; + get_sockaddr(ret, sizeof(ret), addr); + return ret; +} + +int same_sockaddr(struct sockaddr_in *a, struct sockaddr_in *b) +{ + return !((a->sin_family ^ b->sin_family) | + (a->sin_addr.s_addr ^ b->sin_addr.s_addr) | + (a->sin_port ^ b->sin_port)); +} diff --git a/sockaddr.h b/sockaddr.h new file mode 100644 index 0000000..6d1d936 --- /dev/null +++ b/sockaddr.h @@ -0,0 +1,14 @@ +#ifndef _SOCKADDR_H_ +#define _SOCKADDR_H_ + +#include + +int parse_sockaddr(const char *addr, struct sockaddr_in *sa); +int parse_subnet(const char *addr, struct in_addr *net, struct in_addr *mask); + +int get_sockaddr(char *buf, int size, struct sockaddr_in *addr); +char * get_sockaddr_buf(struct sockaddr_in *addr); + +int same_sockaddr(struct sockaddr_in *a, struct sockaddr_in *b); + +#endif /* _SOCKADDR_H_ */