From 6301d8a64029146fa09ba14532af59663e44a014 Mon Sep 17 00:00:00 2001 From: Olaf Rempel Date: Wed, 25 Feb 2009 14:17:23 +0100 Subject: [PATCH] autostart ctorrent --- Makefile | 2 +- connection.c | 87 ++++++++++++++++++- event.c | 1 - spawn.c | 210 +++++++++++++++++++++++++++++++++++++++++++++ spawn.h | 26 ++++++ torrent-stats.c | 16 +++- torrent-stats.conf | 3 + 7 files changed, 337 insertions(+), 8 deletions(-) create mode 100644 spawn.c create mode 100644 spawn.h diff --git a/Makefile b/Makefile index 6189b23..fbc45ca 100644 --- a/Makefile +++ b/Makefile @@ -19,4 +19,4 @@ $(TARGET): $(SRC:.c=.o) clean: rm -rf $(TARGET) *.o *.d --include $(shell find -name *.d 2> /dev/null) +-include $(shell find . -name \*.d 2> /dev/null) diff --git a/connection.c b/connection.c index 6551b5f..d3ee996 100644 --- a/connection.c +++ b/connection.c @@ -20,14 +20,19 @@ #include #include #include -#include +#include +#include +#include + +#include "configfile.h" #include "event.h" #include "httpd.h" #include "linebuffer.h" #include "list.h" #include "logging.h" #include "sockaddr.h" +#include "spawn.h" #include "tcpsocket.h" static LIST_HEAD(torrent_list); @@ -39,7 +44,11 @@ struct torrent_file { /* 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 { @@ -94,6 +103,7 @@ static struct torrent_file * find_create_torrent(const char *fullpath) /* 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; @@ -105,6 +115,68 @@ static struct torrent_file * find_create_torrent(const char *fullpath) 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[64]; + 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 void free_client(struct client_con *con) { list_del(&con->list); @@ -204,16 +276,25 @@ int ctcs_trigger_status(void *privdata) struct torrent_file *torrent; list_for_each_entry(torrent, &torrent_list, list) { - int delete = 0; + int seeder = 0; + int leecher = 0; struct client_con *con; list_for_each_entry(con, &torrent->client_list, list) { write(event_get_fd(con->event), "SENDSTATUS\n", 11); - delete += (con->completed == 0) ? -1 : 1; + if (con->completed) + seeder++; + else + leecher++; } + /* no seeders available and no local seeder running => spawn one */ + if (leecher > 0 && seeder == 0 && torrent->child == NULL) + spawn_torrent_seeder(torrent); + /* delete holds the number of clients to quit */ + int delete = seeder - leecher; list_for_each_entry(con, &torrent->client_list, list) { if (delete <= 0) break; diff --git a/event.c b/event.c index 104d87c..2f255c7 100644 --- a/event.c +++ b/event.c @@ -255,7 +255,6 @@ int event_loop(void) list_del(&entry->list); free(entry); continue; - } if (entry->flags & FD_READ) { diff --git a/spawn.c b/spawn.c new file mode 100644 index 0000000..de69a17 --- /dev/null +++ b/spawn.c @@ -0,0 +1,210 @@ +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include + +#include "list.h" +#include "logging.h" +#include "spawn.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) +{ + /* count args */ + int cnt = 0; + while (argv[cnt++] != NULL); + + /* alloc struct child_process and array of char **argv */ + int size = sizeof(struct child_process) + sizeof(char *) * cnt; + struct child_process *child = malloc(size); + if (child == NULL) { + log_print(LOG_ERROR, "create_child_process(): malloc()"); + return NULL; + } + + memset(child, 0, sizeof(struct child_process)); + + /* argv points to first byte after struct child_process */ + child->argv = (char **)(child +1); + + int i; + for (i = 0; i < ARRAY_SIZE(child->fd); i++) + child->fd[i] = -1; + + /* copy argv */ + for (i = 0; i < cnt; i++) + child->argv[i] = (argv[i] != NULL) ? strdup(argv[i]) : NULL; + + /* copy pwd */ + if (pwd != NULL) + child->pwd = strdup(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 i; + for (i = 0; child->argv[i] != NULL; i++) + free(child->argv[i]); + + if (child->pwd != NULL) + free(child->pwd); + + free(child); +} + +pid_t spawn_child(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) { + log_print(LOG_ERROR, "spawn_child(): stat()"); + return -1; + + /* not a regular file, or not executable */ + } else if (!S_ISREG(stat_buf.st_mode) || !(stat_buf.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))) { + log_print(LOG_ERROR, "spawn_child(): stat()"); + return -1; + } + + /* exit callback */ + child->exit_cb = exit_cb; + child->privdata = privdata; + + /* + * fd[0] - if -1 pipe() and dup2() read-side as child stdin, store write-side for parent + * else dup2() this fd as child stdin + * fd[1] - if -1 pipe() and dup2() write-side as child stdout, store read-side for parent + * else dup2() this fd as child stdout + * fd[2] - if -1 pipe() and dup2() write-side as child stderr, store read-side for parent + * else dup2() this fd as child stderr + */ + int child_pipes[3][2]; + if (child->fd[STDIN_FILENO] == -1) { + if (pipe(child_pipes[STDIN_FILENO]) < 0) { + log_print(LOG_ERROR, "spawn_child(): pipe(STDIN_FILENO)"); + return -1; + } + + } else { + child_pipes[STDIN_FILENO][0] = child->fd[STDIN_FILENO]; + child_pipes[STDIN_FILENO][1] = -1; + } + + if (child->fd[STDOUT_FILENO] == -1) { + if (pipe(child_pipes[STDOUT_FILENO]) < 0) { + log_print(LOG_ERROR, "spawn_child(): pipe(STDOUT_FILENO)"); + return -1; + } + + } else { + child_pipes[STDOUT_FILENO][0] = -1; + child_pipes[STDOUT_FILENO][1] = child->fd[STDOUT_FILENO]; + } + + if (child->fd[STDERR_FILENO] == -1) { + if (pipe(child_pipes[STDERR_FILENO]) < 0) { + log_print(LOG_ERROR, "spawn_child(): pipe(STDERR_FILENO)"); + return -1; + } + + } else { + child_pipes[STDERR_FILENO][0] = -1; + child_pipes[STDERR_FILENO][1] = child->fd[STDERR_FILENO]; + } + + /* add this to list now, so sigchld_handler can find it */ + list_add_tail(&child->list, &child_proc_list); + + child->pid = fork(); + if (child->pid == 0) { /* child */ + close(child_pipes[STDIN_FILENO][1]); + close(child_pipes[STDOUT_FILENO][0]); + close(child_pipes[STDERR_FILENO][0]); + + if (dup2(child_pipes[STDIN_FILENO][0], STDIN_FILENO) < 0) { + perror("dup2(STDIN_FILENO)"); + exit(1); + } + + if (dup2(child_pipes[STDOUT_FILENO][1], STDOUT_FILENO) < 0) { + perror("dup2(STDOUT_FILENO)"); + exit(1); + } + + if (dup2(child_pipes[STDERR_FILENO][1], STDERR_FILENO) < 0) { + perror("dup2(STDERR_FILENO)"); + exit(1); + } + + close(child_pipes[STDIN_FILENO][0]); + close(child_pipes[STDOUT_FILENO][1]); + close(child_pipes[STDERR_FILENO][1]); + + if (child->pwd != NULL && chdir(child->pwd) < 0) { + perror("chdir()"); + exit(1); + } + + execv(child->argv[0], child->argv); + perror("execv()"); + exit(1); + + } else if (child->pid < 0) { /* fork error */ + log_print(LOG_ERROR, "spawn_child(): fork()"); + return -1; + + } else { /* parent */ + if (child->fd[STDIN_FILENO] == -1) { + close(child_pipes[STDIN_FILENO][0]); + child->fd[STDIN_FILENO] = child_pipes[STDIN_FILENO][1]; + } + + if (child->fd[STDOUT_FILENO] == -1) { + close(child_pipes[STDOUT_FILENO][1]); + child->fd[STDOUT_FILENO] = child_pipes[STDOUT_FILENO][0]; + } + + if (child->fd[STDERR_FILENO] == -1) { + close(child_pipes[STDERR_FILENO][1]); + child->fd[STDERR_FILENO] = child_pipes[STDERR_FILENO][0]; + } + + return child->pid; + } +} + +void sigchld_handler(int sig) +{ + struct child_process *child, *tmp; + list_for_each_entry_safe(child, tmp, &child_proc_list, list) { + int status = 0; + int ret = waitpid(child->pid, &status, WNOHANG); + if (ret == -1) { + log_print(LOG_WARN, "sigchld_handler(): waitpid(%d)", child->pid); + continue; + + } else if (ret != 0 && WIFEXITED(status)) { + list_del(&child->list); + + if (child->exit_cb != NULL) + child->exit_cb(child, WEXITSTATUS(status), child->privdata); + + free_child_process(child); + } + } +} diff --git a/spawn.h b/spawn.h new file mode 100644 index 0000000..b010e4e --- /dev/null +++ b/spawn.h @@ -0,0 +1,26 @@ +#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/torrent-stats.c b/torrent-stats.c index 42767a0..f2fd895 100644 --- a/torrent-stats.c +++ b/torrent-stats.c @@ -22,6 +22,7 @@ #include #include +#include #include "configfile.h" #include "connection.h" @@ -30,6 +31,7 @@ #include "list.h" #include "logging.h" #include "sockaddr.h" +#include "spawn.h" #include "tcpsocket.h" #define DEFAULT_CONFIG "torrent-stats.conf" @@ -111,12 +113,12 @@ int main(int argc, char *argv[]) struct passwd *pwl; if (!(pwl = getpwnam(user))) { log_print(LOG_ERROR, "unknown user: %s", user); - exit(-1); + exit(1); } if (setgid(pwl->pw_gid) || setuid(pwl->pw_uid)) { log_print(LOG_ERROR, "setgid/setuid"); - exit(-1); + exit(1); } } @@ -129,7 +131,7 @@ int main(int argc, char *argv[]) if (!debug) { /* start logging */ if (log_init(logfile) < 0) - exit(-1); + exit(1); /* zum daemon mutieren */ daemon(-1, 0); @@ -149,6 +151,14 @@ int main(int argc, char *argv[]) struct timeval tv = { .tv_sec = interval, .tv_usec = 0 }; event_add_timeout(&tv, ctcs_trigger_status, (void *)timeout); + + struct sigaction sigchld_action = { + .sa_handler = sigchld_handler, + .sa_flags = SA_NOCLDSTOP, + }; + sigaction(SIGCHLD, &sigchld_action, NULL); + + event_loop(); return 0; } diff --git a/torrent-stats.conf b/torrent-stats.conf index 99ca6a1..7f4df13 100644 --- a/torrent-stats.conf +++ b/torrent-stats.conf @@ -5,4 +5,7 @@ listen-http 0.0.0.0:8080 status-interval 10 seed-timeout 300 +ctorrent-bin /usr/bin/ctorrent +search-path /home/upload + logfile torrent-stats.log