From 91a2f9a8ce83ee5fbe8d8cc6a87584b3ae7712d3 Mon Sep 17 00:00:00 2001 From: Olaf Rempel Date: Sun, 3 May 2009 14:20:03 +0200 Subject: [PATCH] refactoring --- .gitignore | 1 + Makefile | 2 +- connection.c | 260 ++++++++++++++++++------------------------- connection.h | 8 +- event.c | 15 ++- event.h | 1 + httpd.c | 12 +- httpd.h | 2 + logging.c | 19 +++- pidfile.c | 71 ++++++++++++ pidfile.h | 11 ++ spawn.c => process.c | 19 ++-- process.h | 28 +++++ signals.c | 149 +++++++++++++++++++++++++ signals.h | 12 ++ spawn.h | 26 ----- tcpsocket.c | 52 +++++++++ tcpsocket.h | 8 ++ torrent-stats.c | 99 ++++++---------- torrent-stats.conf | 1 + torrentfile.c | 153 +++++++++++++++++++++++++ torrentfile.h | 31 ++++++ 22 files changed, 719 insertions(+), 261 deletions(-) create mode 100644 pidfile.c create mode 100644 pidfile.h rename spawn.c => process.c (91%) create mode 100644 process.h create mode 100644 signals.c create mode 100644 signals.h delete mode 100644 spawn.h create mode 100644 torrentfile.c create mode 100644 torrentfile.h diff --git a/.gitignore b/.gitignore index 9463277..17864da 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ *.d torrent-stats torrent-stats.log +torrent-stats.pid diff --git a/Makefile b/Makefile index fbc45ca..7c97182 100644 --- a/Makefile +++ b/Makefile @@ -17,6 +17,6 @@ $(TARGET): $(SRC:.c=.o) @$(CC) -c $(CFLAGS) $< -o $@ clean: - rm -rf $(TARGET) *.o *.d + rm -rf $(TARGET) *.o *.d $(TARGET).log $(TARGET).pid -include $(shell find . -name \*.d 2> /dev/null) diff --git a/connection.c b/connection.c index 760ea12..5dcba0d 100644 --- a/connection.c +++ b/connection.c @@ -32,32 +32,10 @@ #include "list.h" #include "logging.h" #include "sockaddr.h" -#include "spawn.h" #include "tcpsocket.h" +#include "torrentfile.h" -static LIST_HEAD(torrent_list); - -struct torrent_file { - /* list of torrent files */ - struct list_head list; - - /* list of clients in this cloud */ - struct list_head client_list; - - /* name of torrentfile */ - char *name; - - /* local seeder process */ - struct child_process *child; -}; - -struct client_con { - struct list_head list; - - struct sockaddr_in addr; - struct event_fd *event; - struct linebuffer *lbuf; - +struct client_stats { /* current bandwidth up/down */ int bw_up; int bw_dn; @@ -70,123 +48,34 @@ struct client_con { int chunk_total; int chunk_avail; int chunk_have; +}; + +struct client_con { + struct list_head list; + struct torrent_file *torrent; + + struct sockaddr_in addr; + struct event_fd *event; + struct linebuffer *lbuf; + + /* download stats */ + struct client_stats stats; /* timestamp when this client completed */ long completed; - - struct torrent_file *torrent; }; -static struct torrent_file * find_create_torrent(const char *fullpath) -{ - /* strip directory from filename */ - const char *filename = strrchr(fullpath, '/'); - if (filename == NULL || *(filename +1) == '\0') - filename = fullpath; - else - filename++; - - /* search for this torrent */ - struct torrent_file *torrent; - list_for_each_entry(torrent, &torrent_list, list) { - if (strcmp(torrent->name, filename) == 0) - return torrent; - } - - /* create a new one */ - torrent = malloc(sizeof(struct torrent_file) + strlen(filename)); - if (torrent == NULL) { - log_print(LOG_WARN, "find_create_torrent(): out of memory"); - return NULL; - } - - /* init fields */ - INIT_LIST_HEAD(&torrent->client_list); - torrent->name = strdup(filename); - torrent->child = NULL; - - /* keep torrent list sorted by name */ - struct torrent_file *search; - list_for_each_entry(search, &torrent_list, list) - if (strcmp(search->name, torrent->name) > 0) - break; - - list_add_tail(&torrent->list, &search->list); - return torrent; -} - -static void child_exit(struct child_process *child, int exit_code, void *privdata) -{ - struct torrent_file *torrent = (struct torrent_file *)privdata; - log_print(LOG_INFO, "ctorrent [pid:%d] was killed (exit:%d)", child->pid, exit_code); - torrent->child = NULL; -} - -static int null_read(int fd, void *privdata) -{ - char buf[256]; - int len; - - do { - len = read(fd, buf, sizeof(buf)); - } while (len == sizeof(buf)); - - return !(len > 0); -} - -static int spawn_torrent_seeder(struct torrent_file *torrent) -{ - // TODO: more than one search path? - const char *path = config_get_string("global", "search-path", NULL); - if (path == NULL) { - log_print(LOG_WARN, "requesting torrentfile, but no search path given"); - return -1; - } - - char buf[256]; - int len = snprintf(buf, sizeof(buf), "%s/%s", path, torrent->name); - if (len < 0 || len >= sizeof(buf)) { - log_print(LOG_WARN, "filename > max"); - return -1; - } - - struct stat statbuf; - if (stat(buf, &statbuf) < 0) { - log_print(LOG_WARN, "torrent file not found: %s", buf); - return -1; - } - - const char *ctorrent_bin = config_get_string("global", "ctorrent-bin", "/usr/bin/ctorrent"); - // TODO: statserv is not always localhost - char *const args[] = { (char *)ctorrent_bin, "-S", "127.0.0.1:2780", "-f", buf, NULL }; - - torrent->child = alloc_child_process(args, path); - if (spawn_child(torrent->child, child_exit, torrent) < 0) { - log_print(LOG_ERROR, "spawn_child(%s)", args[0]); - free_child_process(torrent->child); - torrent->child = NULL; - return -1; - } - - log_print(LOG_INFO, "spawned ctorrent for %s [pid:%d]", torrent->name, torrent->child->pid); - - /* just read all output from ctorrent to /dev/null */ - event_add_readfd(NULL, torrent->child->fd[STDOUT_FILENO], null_read, NULL); - event_add_readfd(NULL, torrent->child->fd[STDERR_FILENO], null_read, NULL); - - return 0; -} +static struct event_fd *listen_event; +static struct event_fd *httpd_event; +static struct event_timeout *timeout_event; static void free_client(struct client_con *con) { list_del(&con->list); /* remove torrents without clients */ - if (list_empty(&con->torrent->client_list)) { - list_del(&con->torrent->list); - free(con->torrent->name); - free(con->torrent); - } + if (list_empty(&con->torrent->client_list)) + destroy_torrent(con->torrent); close(event_get_fd(con->event)); event_remove_fd(con->event); @@ -194,9 +83,10 @@ static void free_client(struct client_con *con) free(con); } -static int data_cb(int fd, void *privdata) +static int ctcs_data_handler(int fd, void *privdata) { struct client_con *con = (struct client_con *)privdata; + struct client_stats *stats = &con->stats; /* get data from socket */ if (linebuffer_readfd(con->lbuf, fd) < 0) { @@ -210,8 +100,8 @@ static int data_cb(int fd, void *privdata) if (strncmp(line, "CTBW ", 5) == 0) { int bwup, bwdn, liup, lidn; if (sscanf(line +5, "%d,%d %d,%d", &bwdn, &bwup, &lidn, &liup) == 4) { - con->bw_up = bwup; - con->bw_dn = bwdn; + stats->bw_up = bwup; + stats->bw_dn = bwdn; } /* status update (assume protocol v2) */ @@ -227,22 +117,24 @@ static int data_cb(int fd, void *privdata) &bwdn, &bwup, &totdn, &totup, &lidn, &liup, &cache) == 15) { - con->total_up = totup; - con->total_dn = totdn; - con->chunk_have = chunk1; - con->chunk_total = chunk2; - con->chunk_avail = chunk3; + stats->bw_up = bwup; + stats->bw_dn = bwdn; + stats->total_up = totup; + stats->total_dn = totdn; + stats->chunk_have = chunk1; + stats->chunk_total = chunk2; + stats->chunk_avail = chunk3; } /* update torrent-file */ } else if (strncmp(line, "CTORRENT ", 9) == 0) { char *filename = strrchr(line +9, ' '); - if (filename != NULL) { + if (filename != NULL && strcmp(filename, con->torrent->name) != 0) { struct torrent_file *torrent = find_create_torrent(filename +1); if (torrent != NULL) { list_del(&con->list); + list_add_tail(&con->list, &torrent->client_list); con->torrent = torrent; - list_add_tail(&con->list, &con->torrent->client_list); } } } @@ -250,7 +142,7 @@ static int data_cb(int fd, void *privdata) } /* client completed? */ - if (con->chunk_have == con->chunk_total && con->chunk_total != 0 && con->completed == 0) { + if (stats->chunk_have == stats->chunk_total && stats->chunk_total != 0 && con->completed == 0) { con->completed = time(NULL); list_del(&con->list); @@ -270,7 +162,7 @@ static int data_cb(int fd, void *privdata) return 0; } -int ctcs_trigger_status(void *privdata) +static int ctcs_trigger_status(void *privdata) { long timeout = time(NULL) - (int)privdata; @@ -291,7 +183,7 @@ int ctcs_trigger_status(void *privdata) /* no seeders available and no local seeder running => spawn one */ if (leecher > 0 && seeder == 0 && torrent->child == NULL) - spawn_torrent_seeder(torrent); + seed_torrent(torrent); /* delete holds the number of clients to quit */ int delete = seeder - leecher; @@ -309,7 +201,7 @@ int ctcs_trigger_status(void *privdata) return 0; } -int ctcs_accept_handler(int fd, void *privdata) +static int ctcs_accept_handler(int fd, void *privdata) { struct client_con *con = malloc(sizeof(struct client_con)); if (con == NULL) { @@ -326,16 +218,15 @@ int ctcs_accept_handler(int fd, void *privdata) return 0; } - unsigned int i = sizeof(con->addr); - int sockfd = accept(fd, (struct sockaddr *)&con->addr, &i); + int sockfd = tcp_accept(fd, &con->addr); if (sockfd < 0) { - log_print(LOG_WARN, "accept_cb(): accept()"); + log_print(LOG_WARN, "accept_cb(): tcp_accept()"); free(con->lbuf); free(con); return 0; } - con->event = event_add_readfd(NULL, sockfd, data_cb, con); + con->event = event_add_readfd(NULL, sockfd, ctcs_data_handler, con); /* assign default torrent */ con->torrent = find_create_torrent("[unknown]"); @@ -343,7 +234,7 @@ int ctcs_accept_handler(int fd, void *privdata) return 0; } -int ctcs_httpd_show(struct httpd_con *con, void *privdata) +static int ctcs_httpd_show(struct httpd_con *con, void *privdata) { struct linebuffer *lbuf = create_linebuffer(16384); if (lbuf == NULL) { @@ -365,12 +256,19 @@ int ctcs_httpd_show(struct httpd_con *con, void *privdata) struct client_con *tmp; list_for_each_entry(tmp, &torrent->client_list, list) { + struct client_stats *stats = &tmp->stats; linebuffer_printf(lbuf, "%s%3.2lf%% (%d/%d/%d)", get_sockaddr_buf(&tmp->addr), - (double)tmp->chunk_have / (double)tmp->chunk_total * 100.0, tmp->chunk_have, tmp->chunk_total, tmp->chunk_avail); + (double)stats->chunk_have / (double)stats->chunk_total * 100.0, + stats->chunk_have, + stats->chunk_total, + stats->chunk_avail); linebuffer_printf(lbuf, "%llu (%d)%llu (%d)%s", - tmp->total_dn, tmp->bw_dn, tmp->total_up, tmp->bw_up, + stats->total_dn, + stats->bw_dn, + stats->total_up, + stats->bw_up, (tmp->completed != 0) ? ctime(&tmp->completed) : "-"); linebuffer_printf(lbuf, "Quit\n", get_sockaddr_buf(&tmp->addr)); @@ -384,7 +282,7 @@ int ctcs_httpd_show(struct httpd_con *con, void *privdata) return 0; } -int ctcs_httpd_quit(struct httpd_con *con, void *privdata) +static int ctcs_httpd_quit(struct httpd_con *con, void *privdata) { if (con->req_arg_cnt == 2 && strncmp(con->req_args[1], "client=", 7) == 0) { struct sockaddr_in addr; @@ -406,3 +304,61 @@ int ctcs_httpd_quit(struct httpd_con *con, void *privdata) write(con->fd, text, strlen(text)); return 0; } + +int ctcs_init(void) +{ + const char *listen_addr = config_get_string("global", "listen", "0.0.0.0:2780"); + listen_event = tcp_listen_event(listen_addr, ctcs_accept_handler, NULL); + if (listen_event == NULL) + return -1; + + const char *httpd_addr = config_get_string("global", "listen-http", "0.0.0.0:8080"); + httpd_event = tcp_listen_event(httpd_addr, httpd_accept_handler, NULL); + if (httpd_event == NULL) { + event_remove_fd(listen_event); + close(event_get_fd(listen_event)); + return -1; + } + + httpd_add_cb("/quit", 0, ctcs_httpd_quit, NULL); + httpd_add_cb("/", 1, ctcs_httpd_show, NULL); + + int interval = config_get_int("global", "status-interval", 10); + int timeout = config_get_int("global", "seed-timeout", 60); + + struct timeval tv = { .tv_sec = interval, .tv_usec = 0 }; + timeout_event = event_add_timeout(&tv, ctcs_trigger_status, (void *)timeout); + if (timeout_event == NULL) { + event_remove_fd(httpd_event); + close(event_get_fd(httpd_event)); + event_remove_fd(listen_event); + close(event_get_fd(listen_event)); + return -1; + } + + return 0; +} + +int ctcs_free(void) +{ + struct torrent_file *torrent, *torrent2; + list_for_each_entry_safe(torrent, torrent2, &torrent_list, list) { + + struct client_con *con, *con2; + list_for_each_entry_safe(con, con2, &torrent->client_list, list) { + free_client(con); + } + } + + event_remove_timeout(timeout_event); + + event_remove_fd(httpd_event); + close(event_get_fd(httpd_event)); + + httpd_free(); + + event_remove_fd(listen_event); + close(event_get_fd(listen_event)); + + return 0; +} diff --git a/connection.h b/connection.h index fa18241..18d13a2 100644 --- a/connection.h +++ b/connection.h @@ -1,11 +1,7 @@ #ifndef _CONNECTION_H_ #define _CONNECTION_H_ -#include "httpd.h" - -int ctcs_trigger_status(void *privdata); -int ctcs_accept_handler(int fd, void *privdata); -int ctcs_httpd_show(struct httpd_con *con, void *privdata); -int ctcs_httpd_quit(struct httpd_con *con, void *privdata); +int ctcs_init(void); +int ctcs_free(void); #endif /* _CONNECTION_H_ */ diff --git a/event.c b/event.c index 2f255c7..935a331 100644 --- a/event.c +++ b/event.c @@ -30,6 +30,7 @@ static LIST_HEAD(event_fd_list); static LIST_HEAD(event_timeout_list); +static int leave_loop; struct event_fd { struct list_head list; @@ -198,6 +199,11 @@ void event_remove_timeout(struct event_timeout *entry) entry->flags |= EVENT_DELETE; } +void event_loop_break(void) +{ + leave_loop = 1; +} + int event_loop(void) { fd_set *fdsets = malloc(sizeof(fd_set) * 2); @@ -206,7 +212,8 @@ int event_loop(void) return -1; } - while (1) { + leave_loop = 0; + while (!leave_loop) { struct timeval timeout, *timeout_p = NULL; if (!list_empty(&event_timeout_list)) { struct timeval now; @@ -247,6 +254,7 @@ int event_loop(void) fd_set *readfds = NULL, *writefds = NULL; struct event_fd *entry, *tmp; + int maxfd = -1; list_for_each_entry_safe(entry, tmp, &event_fd_list, list) { entry->flags &= ~EVENT_NEW; @@ -272,9 +280,11 @@ int event_loop(void) } FD_SET(entry->fd, writefds); } + + maxfd = (entry->fd > maxfd) ? entry->fd : maxfd; } - int i = select(FD_SETSIZE, readfds, writefds, NULL, timeout_p); + int i = select(maxfd +1, readfds, writefds, NULL, timeout_p); if (i <= 0) { /* On error, -1 is returned, and errno is set * appropriately; the sets and timeout become @@ -295,4 +305,5 @@ int event_loop(void) } } free(fdsets); + return 0; } diff --git a/event.h b/event.h index 05ae63f..0413b86 100644 --- a/event.h +++ b/event.h @@ -37,6 +37,7 @@ struct event_timeout * event_add_timeout( void event_remove_timeout(struct event_timeout *entry); +void event_loop_break(void); int event_loop(void); #endif /* _EVENT_H_ */ diff --git a/httpd.c b/httpd.c index 8ba2ef4..cc36561 100644 --- a/httpd.c +++ b/httpd.c @@ -204,8 +204,7 @@ int httpd_accept_handler(int fd, void *privdata) memset(con, 0, sizeof(struct httpd_con)); - unsigned int i = sizeof(con->addr); - con->fd = accept(fd, (struct sockaddr *)&con->addr, &i); + con->fd = tcp_accept(fd, &con->addr); if (con->fd < 0) { free(con); return 0; @@ -242,3 +241,12 @@ int httpd_remove_cb(struct httpd_callback *hcb) free(hcb); return 0; } + +int httpd_free(void) +{ + struct httpd_callback *hcb, *hcb2; + list_for_each_entry_safe(hcb, hcb2, &httpd_cb_list, list) + httpd_remove_cb(hcb); + + return 0; +} diff --git a/httpd.h b/httpd.h index 82df687..83dd855 100644 --- a/httpd.h +++ b/httpd.h @@ -39,4 +39,6 @@ int httpd_remove_cb(struct httpd_callback *cb); int httpd_accept_handler(int fd, void *privdata); +int httpd_free(void); + #endif // _HTTP_H_ diff --git a/logging.c b/logging.c index fd0ab0b..ddc1128 100644 --- a/logging.c +++ b/logging.c @@ -18,10 +18,12 @@ ***************************************************************************/ #include #include +#include + #include #include #include -#include +#include #include "logging.h" @@ -75,11 +77,15 @@ int log_print(int prio, const char *fmt, ...) void log_close(void) { - if (buffer) + if (buffer) { free(buffer); + buffer = NULL; + } - if (log_fd) + if (log_fd) { fclose(log_fd); + log_fd = NULL; + } } int log_init(const char *logfile) @@ -92,6 +98,13 @@ int log_init(const char *logfile) 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; } diff --git a/pidfile.c b/pidfile.c new file mode 100644 index 0000000..9de2d3d --- /dev/null +++ b/pidfile.c @@ -0,0 +1,71 @@ +#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'; + + 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/spawn.c b/process.c similarity index 91% rename from spawn.c rename to process.c index de69a17..75cb2c3 100644 --- a/spawn.c +++ b/process.c @@ -15,13 +15,13 @@ #include "list.h" #include "logging.h" -#include "spawn.h" +#include "process.h" #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0])) static LIST_HEAD(child_proc_list); -struct child_process * alloc_child_process(char *const argv[], const char *pwd) +struct child_process * childproc_alloc(char *const argv[], const char *pwd) { /* count args */ int cnt = 0; @@ -55,9 +55,12 @@ struct child_process * alloc_child_process(char *const argv[], const char *pwd) return child; } -/* TODO: really export this to users? must not called when child still running */ -void free_child_process(struct child_process *child) +int childproc_free(struct child_process *child) { + /* child already running, return error */ + if (child->pid != 0) + return -1; + int i; for (i = 0; child->argv[i] != NULL; i++) free(child->argv[i]); @@ -66,9 +69,10 @@ void free_child_process(struct child_process *child) free(child->pwd); free(child); + return 0; } -pid_t spawn_child(struct child_process *child, void (*exit_cb)(struct child_process *child, int exit_code, void *privdata), void *privdata) +pid_t childproc_fork(struct child_process *child, void (*exit_cb)(struct child_process *child, int exit_code, void *privdata), void *privdata) { struct stat stat_buf; if (stat(child->argv[0], &stat_buf) != 0) { @@ -188,7 +192,7 @@ pid_t spawn_child(struct child_process *child, void (*exit_cb)(struct child_proc } } -void sigchld_handler(int sig) +void childproc_cleanup(void) { struct child_process *child, *tmp; list_for_each_entry_safe(child, tmp, &child_proc_list, list) { @@ -204,7 +208,8 @@ void sigchld_handler(int sig) if (child->exit_cb != NULL) child->exit_cb(child, WEXITSTATUS(status), child->privdata); - free_child_process(child); + child->pid = 0; + childproc_free(child); } } } diff --git a/process.h b/process.h new file mode 100644 index 0000000..eccdb29 --- /dev/null +++ b/process.h @@ -0,0 +1,28 @@ +#ifndef _PROCESS_H_ +#define _PROCESS_H_ + +#include + +#include "list.h" + +struct child_process { + struct list_head list; + + char **argv; + char *pwd; + int fd[3]; + + pid_t pid; + + void (*exit_cb)(struct child_process *child, int exit_code, void *privdata); + void *privdata; +}; + +struct child_process * childproc_alloc(char *const argv[], const char *pwd); +int childproc_free(struct child_process *child); + +pid_t childproc_fork(struct child_process *child, void (*exit_cb)(struct child_process *child, int exit_code, void *privdata), void *privdata); + +void childproc_cleanup(void); + +#endif // _SPAWN_H_ diff --git a/signals.c b/signals.c new file mode 100644 index 0000000..f640a3b --- /dev/null +++ b/signals.c @@ -0,0 +1,149 @@ +/*************************************************************************** + * 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); +}; + +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_set_callback(int signum, void (*callback)(void)) +{ + 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; + 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(); + } + + 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..f2c8a06 --- /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_set_callback(int signum, void (*callback)(void)); + +int signal_init(void); + +#endif // _SIGNALS_H diff --git a/spawn.h b/spawn.h deleted file mode 100644 index b010e4e..0000000 --- a/spawn.h +++ /dev/null @@ -1,26 +0,0 @@ -#ifndef _SPAWN_H_ -#define _SPAWN_H_ - -#include - -struct child_process { - struct list_head list; - - char **argv; - char *pwd; - int fd[3]; - - pid_t pid; - - void (*exit_cb)(struct child_process *child, int exit_code, void *privdata); - void *privdata; -}; - -struct child_process * alloc_child_process(char *const argv[], const char *pwd); -void free_child_process(struct child_process *child); - -pid_t spawn_child(struct child_process *child, void (*exit_cb)(struct child_process *child, int exit_code, void *privdata), void *privdata); - -void sigchld_handler(int sig); - -#endif // _SPAWN_H_ diff --git a/tcpsocket.c b/tcpsocket.c index 1c55106..08cb01f 100644 --- a/tcpsocket.c +++ b/tcpsocket.c @@ -25,6 +25,7 @@ #include #include +#include "event.h" #include "logging.h" #include "sockaddr.h" @@ -49,6 +50,11 @@ int tcp_listen(struct sockaddr_in *sa) return -1; } + if (fcntl(sock, F_SETFD, FD_CLOEXEC) < 0) { + log_print(LOG_WARN, "tcp_listen_socket(): fcntl(FD_CLOEXEC)"); + return -1; + } + if (listen(sock, 8)) { log_print(LOG_ERROR, "tcp_listen_socket(): listen()"); close(sock); @@ -57,6 +63,24 @@ int tcp_listen(struct sockaddr_in *sa) return sock; } +int tcp_accept(int fd, struct sockaddr_in *sa) +{ + unsigned int i = sizeof(struct sockaddr_in); + int sock = accept(fd, (struct sockaddr *)sa, &i); + if (sock < 0) { + log_print(LOG_WARN, "tcp_accept(): accept()"); + return -1; + } + + if (fcntl(sock, F_SETFD, FD_CLOEXEC) < 0) { + log_print(LOG_WARN, "tcp_accept(): fcntl(FD_CLOEXEC)"); + close(sock); + return -1; + } + + return sock; +} + int tcp_connect(struct sockaddr_in *sa) { int sock = socket(AF_INET, SOCK_STREAM, 0); @@ -65,6 +89,11 @@ int tcp_connect(struct sockaddr_in *sa) return -1; } + if (fcntl(sock, F_SETFD, FD_CLOEXEC) < 0) { + log_print(LOG_WARN, "tcp_connect_socket(): fcntl(FD_CLOEXEC)"); + return -1; + } + int ret = connect(sock, (struct sockaddr *)sa, sizeof(*sa)); if (ret != 0) { log_print(LOG_ERROR, "tcp_connect(): connect(%s)", get_sockaddr_buf(sa)); @@ -83,6 +112,11 @@ int tcp_connect_nonblock(struct sockaddr_in *sa) return -1; } + if (fcntl(sock, F_SETFD, FD_CLOEXEC) < 0) { + log_print(LOG_WARN, "tcp_connect_nonblock(): fcntl(FD_CLOEXEC)"); + return -1; + } + int flags = fcntl(sock, F_GETFL, 0); if (flags < 0) { log_print(LOG_ERROR, "tcp_connect_nonblock(): fcntl(F_GETFL)"); @@ -133,3 +167,21 @@ int tcp_connect_error(int fd) return 0; } + +struct event_fd * tcp_listen_event(const char *address, void *accept_handler, void *accept_privdata) +{ + struct sockaddr_in addr; + + if (parse_sockaddr(address, &addr) < 0) { + log_print(LOG_WARN, "tcp_listen_event(): invalid address"); + return NULL; + } + + int sock = tcp_listen(&addr); + if (sock < 0) { + log_print(LOG_WARN, "tcp_listen_event(): tcp_listen()"); + return NULL; + } + + return event_add_readfd(NULL, sock, accept_handler, accept_privdata); +} diff --git a/tcpsocket.h b/tcpsocket.h index 606dbab..290443f 100644 --- a/tcpsocket.h +++ b/tcpsocket.h @@ -3,10 +3,18 @@ #include +#include "event.h" + int tcp_listen(struct sockaddr_in *sa); +int tcp_accept(int fd, struct sockaddr_in *sa); int tcp_connect(struct sockaddr_in *sa); int tcp_connect_nonblock(struct sockaddr_in *sa); int tcp_connect_error(int fd); +struct event_fd * tcp_listen_event( + const char *address, + void *accept_handler, + void *accept_privdata); + #endif // _TCPSOCKET_H_ diff --git a/torrent-stats.c b/torrent-stats.c index f2fd895..c018b8d 100644 --- a/torrent-stats.c +++ b/torrent-stats.c @@ -21,54 +21,30 @@ #include #include -#include #include #include "configfile.h" #include "connection.h" #include "event.h" -#include "httpd.h" -#include "list.h" #include "logging.h" -#include "sockaddr.h" -#include "spawn.h" -#include "tcpsocket.h" +#include "pidfile.h" +#include "process.h" +#include "signals.h" #define DEFAULT_CONFIG "torrent-stats.conf" #define DEFAULT_LOGFILE "torrent-stats.log" +#define DEFAULT_PIDFILE "torrent-stats.pid" static struct option opts[] = { {"config", 1, 0, 'c'}, - {"user", 1, 0, 'u'}, {"debug", 0, 0, 'd'}, {"help", 0, 0, 'h'}, {0, 0, 0, 0} }; -static int listen_cb(const char *parameter, void *privdata) -{ - struct sockaddr_in addr; - - if (parse_sockaddr(parameter, &addr) < 0) { - log_print(LOG_WARN, "listen_cb(): invalid address"); - return -1; - } - - int sockfd = tcp_listen(&addr); - if (sockfd < 0) { - log_print(LOG_WARN, "listen_cb(): tcp_listen()"); - return -1; - } - - log_print(LOG_INFO, "listen on %s", get_sockaddr_buf(&addr)); - event_add_readfd(NULL, sockfd, privdata, NULL); - return 0; -} - int main(int argc, char *argv[]) { char *config = DEFAULT_CONFIG; - char *user = NULL; int debug = 0; int code, arg = 0; @@ -80,10 +56,6 @@ int main(int argc, char *argv[]) config = optarg; break; - case 'u': /* user */ - user = optarg; - break; - case 'd': /* debug */ debug = 1; break; @@ -93,7 +65,6 @@ int main(int argc, char *argv[]) "Options: \n" " --config -c configfile use this configfile\n" " --debug -d do not fork and log to stderr\n" - " --user -u username change uid to username\n" " --help -h this help\n" "\n"); exit(0); @@ -108,20 +79,6 @@ int main(int argc, char *argv[]) } } while (code != -1); - /* userwechsel */ - if (user) { - struct passwd *pwl; - if (!(pwl = getpwnam(user))) { - log_print(LOG_ERROR, "unknown user: %s", user); - exit(1); - } - - if (setgid(pwl->pw_gid) || setuid(pwl->pw_uid)) { - log_print(LOG_ERROR, "setgid/setuid"); - exit(1); - } - } - /* parse config file */ if (config_parse(config) < 0) exit(1); @@ -129,36 +86,54 @@ int main(int argc, char *argv[]) /* check logfile */ const char *logfile = config_get_string("global", "logfile", DEFAULT_LOGFILE); if (!debug) { + /* check pidfile */ + const char *pidfile = config_get_string("global", "pidfile", DEFAULT_PIDFILE); + if (pidfile_check(pidfile, 1) != 0) { + log_print(LOG_ERROR, "torrent-stats already running"); + exit(1); + } + /* start logging */ if (log_init(logfile) < 0) exit(1); /* zum daemon mutieren */ daemon(-1, 0); + + /* create pidfile */ + if (pidfile_create(pidfile) < 0) { + log_print(LOG_ERROR, "failed to create pidfile %s", pidfile); + exit(1); + } } - log_print(LOG_INFO, "torrent-stats started (user: %s, pid: %d)", getpwuid(getuid())->pw_name, getpid()); + log_print(LOG_INFO, "torrent-stats started (pid: %d)", getpid()); - config_get_strings("global", "listen", listen_cb, ctcs_accept_handler); - config_get_strings("global", "listen-http", listen_cb, httpd_accept_handler); + signal_init(); + signal_set_callback(SIGCHLD, childproc_cleanup); + signal_set_callback(SIGHUP, event_loop_break); - httpd_add_cb("/quit", 0, ctcs_httpd_quit, NULL); - httpd_add_cb("/", 1, ctcs_httpd_show, NULL); + while (1) { + if (ctcs_init() < 0) + break; - int interval = config_get_int("global", "status-interval", 10); - int timeout = config_get_int("global", "seed-timeout", 300); + event_loop(); - struct timeval tv = { .tv_sec = interval, .tv_usec = 0 }; - event_add_timeout(&tv, ctcs_trigger_status, (void *)timeout); + ctcs_free(); + log_close(); - struct sigaction sigchld_action = { - .sa_handler = sigchld_handler, - .sa_flags = SA_NOCLDSTOP, - }; - sigaction(SIGCHLD, &sigchld_action, NULL); + config_free(); + if (config_parse(config) < 0) + break; + + logfile = config_get_string("global", "logfile", DEFAULT_LOGFILE); + if (!debug && log_init(logfile) < 0) + break; + + log_print(LOG_INFO, "torrent-stats restarted (pid: %d)", getpid()); + } - event_loop(); return 0; } diff --git a/torrent-stats.conf b/torrent-stats.conf index e1bbd43..176ee00 100644 --- a/torrent-stats.conf +++ b/torrent-stats.conf @@ -9,3 +9,4 @@ ctorrent-bin /usr/bin/ctorrent search-path /home/upload logfile torrent-stats.log +pidfile torrent-stats.pid diff --git a/torrentfile.c b/torrentfile.c new file mode 100644 index 0000000..49f5251 --- /dev/null +++ b/torrentfile.c @@ -0,0 +1,153 @@ +/*************************************************************************** + * 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 + +#include "configfile.h" +#include "event.h" +#include "list.h" +#include "logging.h" +#include "process.h" +#include "torrentfile.h" + +LIST_HEAD(torrent_list); + +struct torrent_file * find_create_torrent(const char *fullpath) +{ + /* strip directory from filename */ + const char *filename = strrchr(fullpath, '/'); + if (filename == NULL || *(filename +1) == '\0') + filename = fullpath; + else + filename++; + + /* search for this torrent */ + struct torrent_file *torrent; + list_for_each_entry(torrent, &torrent_list, list) { + if (strcmp(torrent->name, filename) == 0) + return torrent; + } + + /* create a new one */ + torrent = malloc(sizeof(struct torrent_file) + strlen(filename)); + if (torrent == NULL) { + log_print(LOG_WARN, "find_create_torrent(): out of memory"); + return NULL; + } + + /* init fields */ + INIT_LIST_HEAD(&torrent->client_list); + torrent->name = strdup(filename); + torrent->destroy = 0; + torrent->child = NULL; + + /* keep torrent list sorted by name */ + struct torrent_file *search; + list_for_each_entry(search, &torrent_list, list) + if (strcmp(search->name, torrent->name) > 0) + break; + + list_add_tail(&torrent->list, &search->list); + return torrent; +} + +int destroy_torrent(struct torrent_file *torrent) +{ + /* remove us from list */ + list_del(&torrent->list); + + /* check if we're seeding, destroy after sigchld */ + if (torrent->child != NULL) { + torrent->destroy = 1; + kill(SIGTERM, torrent->child->pid); + return 0; + } + + free(torrent->name); + free(torrent); + return 0; +} + +static void child_exit(struct child_process *child, int exit_code, void *privdata) +{ + struct torrent_file *torrent = (struct torrent_file *)privdata; + log_print(LOG_INFO, "ctorrent [pid:%d] was killed (exit:%d)", child->pid, exit_code); + torrent->child = NULL; + + close(child->fd[STDIN_FILENO]); + close(child->fd[STDOUT_FILENO]); + close(child->fd[STDERR_FILENO]); + + if (torrent->destroy) + destroy_torrent(torrent); +} + +static int null_read(int fd, void *privdata) +{ + char buf[256]; + + int len = read(fd, buf, sizeof(buf)); + return !(len > 0); +} + +int seed_torrent(struct torrent_file *torrent) +{ + const char *path = config_get_string("global", "search-path", NULL); + if (path == NULL) { + log_print(LOG_WARN, "requesting torrentfile, but no search path given"); + return -1; + } + + char buf[256]; + int len = snprintf(buf, sizeof(buf), "%s/%s", path, torrent->name); + if (len < 0 || len >= sizeof(buf)) { + log_print(LOG_WARN, "filename > max"); + return -1; + } + + struct stat statbuf; + if (stat(buf, &statbuf) < 0) { + log_print(LOG_WARN, "torrent file not found: %s", buf); + return -1; + } + + const char *ctorrent_bin = config_get_string("global", "ctorrent-bin", "/usr/bin/ctorrent"); + const char *statserv = config_get_string("global", "statserv", "127.0.0.1:2780"); + char *const args[] = { (char *)ctorrent_bin, "-S", (char *)statserv, "-f", buf, NULL }; + + torrent->child = childproc_alloc(args, path); + if (childproc_fork(torrent->child, child_exit, torrent) < 0) { + log_print(LOG_ERROR, "spawn_child(%s)", args[0]); + childproc_free(torrent->child); + torrent->child = NULL; + return -1; + } + + log_print(LOG_INFO, "spawned ctorrent for %s [pid:%d]", torrent->name, torrent->child->pid); + + /* just read all output from ctorrent to /dev/null */ + event_add_readfd(NULL, torrent->child->fd[STDOUT_FILENO], null_read, NULL); + event_add_readfd(NULL, torrent->child->fd[STDERR_FILENO], null_read, NULL); + + return 0; +} diff --git a/torrentfile.h b/torrentfile.h new file mode 100644 index 0000000..c7cddec --- /dev/null +++ b/torrentfile.h @@ -0,0 +1,31 @@ +#ifndef _TORRENTFILE_H_ +#define _TORRENTFILE_H_ + +#include "list.h" +#include "spawn.h" + +extern struct list_head torrent_list; + +struct torrent_file { + /* list of torrent files */ + struct list_head list; + + /* list of clients in this cloud */ + struct list_head client_list; + + /* name of torrentfile */ + char *name; + + /* local seeder process */ + struct child_process *child; + + /* destroy torrent after child-exit */ + int destroy; +}; + +struct torrent_file * find_create_torrent(const char *fullpath); +int destroy_torrent(struct torrent_file *torrent); + +int seed_torrent(struct torrent_file *torrent); + +#endif /* _TORRENTFILE_H_ */