/*************************************************************************** * 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 "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); 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; /* current bandwidth up/down */ int bw_up; int bw_dn; /* total bytes up/down */ unsigned long long total_up; unsigned long long total_dn; /* clients cloud view */ int chunk_total; int chunk_avail; int chunk_have; /* 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 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); } close(event_get_fd(con->event)); event_remove_fd(con->event); free(con->lbuf); free(con); } static int data_cb(int fd, void *privdata) { struct client_con *con = (struct client_con *)privdata; /* get data from socket */ if (linebuffer_readfd(con->lbuf, fd) < 0) { free_client(con); return -1; } char *line; while ((line = linebuffer_getline(con->lbuf, NULL)) != NULL) { /* bandwidth update */ 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; } /* status update (assume protocol v2) */ } else if (strncmp(line, "CTSTATUS ", 9) == 0) { int seeds1 = 0, seeds2 = 0, leech1 = 0, leech2 = 0, count = 0; int chunk1 = 0, chunk2 = 0, chunk3 = 0, bwdn = 0, bwup = 0; int lidn = 0, liup = 0, cache = 0; unsigned long long totdn = 0, totup = 0; if (sscanf(line +9, "%d:%d/%d:%d/%d %d/%d/%d %d,%d %llu,%llu %d,%d %d", &seeds1, &seeds2, &leech1, &leech2, &count, &chunk1, &chunk2, &chunk3, &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; } /* update torrent-file */ } else if (strncmp(line, "CTORRENT ", 9) == 0) { char *filename = strrchr(line +9, ' '); if (filename != NULL) { struct torrent_file *torrent = find_create_torrent(filename +1); if (torrent != NULL) { list_del(&con->list); con->torrent = torrent; list_add_tail(&con->list, &con->torrent->client_list); } } } linebuffer_freeline(con->lbuf); } /* client completed? */ if (con->chunk_have == con->chunk_total && con->chunk_total != 0 && con->completed == 0) { con->completed = time(NULL); list_del(&con->list); /* sorted insert, completed on top of list, ordered by complete-timestamp */ struct client_con *search; list_for_each_entry(search, &con->torrent->client_list, list) { if (search->completed == 0) break; if (search->completed > con->completed) break; } list_add_tail(&con->list, &search->list); return 0; } return 0; } int ctcs_trigger_status(void *privdata) { long timeout = time(NULL) - (int)privdata; struct torrent_file *torrent; list_for_each_entry(torrent, &torrent_list, list) { 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); 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; if (con->completed == 0 || con->completed > timeout) continue; write(event_get_fd(con->event), "CTQUIT\n", 7); delete--; } } return 0; } int ctcs_accept_handler(int fd, void *privdata) { struct client_con *con = malloc(sizeof(struct client_con)); if (con == NULL) { log_print(LOG_WARN, "accept_cb(): out of memory"); return 0; } memset(con, 0, sizeof(struct client_con)); con->lbuf = create_linebuffer(1024); if (con->lbuf == NULL) { log_print(LOG_WARN, "accept_cb(): out of memory"); free(con); return 0; } unsigned int i = sizeof(con->addr); int sockfd = accept(fd, (struct sockaddr *)&con->addr, &i); if (sockfd < 0) { log_print(LOG_WARN, "accept_cb(): accept()"); free(con->lbuf); free(con); return 0; } con->event = event_add_readfd(NULL, sockfd, data_cb, con); /* assign default torrent */ con->torrent = find_create_torrent("[unknown]"); list_add_tail(&con->list, &con->torrent->client_list); return 0; } int ctcs_httpd_show(struct httpd_con *con, void *privdata) { struct linebuffer *lbuf = create_linebuffer(16384); if (lbuf == NULL) { httpd_send_error(con, "500 ERROR", "Out of Memory"); return -1; } linebuffer_printf(lbuf, "HTTP/1.0 200 OK\r\nContent-Type: text/html\r\nConnection: close\r\n\r\n"); linebuffer_printf(lbuf, "

ctorrent stats

\n"); struct torrent_file *torrent; list_for_each_entry(torrent, &torrent_list, list) { if (list_empty(&torrent->client_list)) continue; linebuffer_printf(lbuf, "\n\n", torrent->name); linebuffer_printf(lbuf, ""); linebuffer_printf(lbuf, "\n"); struct client_con *tmp; list_for_each_entry(tmp, &torrent->client_list, list) { linebuffer_printf(lbuf, "", get_sockaddr_buf(&tmp->addr), (double)tmp->chunk_have / (double)tmp->chunk_total * 100.0, tmp->chunk_have, tmp->chunk_total, tmp->chunk_avail); linebuffer_printf(lbuf, "", tmp->total_dn, tmp->bw_dn, tmp->total_up, tmp->bw_up, (tmp->completed != 0) ? ctime(&tmp->completed) : "-"); linebuffer_printf(lbuf, "\n", get_sockaddr_buf(&tmp->addr)); } linebuffer_printf(lbuf, "
%s
Client IP:PortChunks (have/total/avail)Download total(current)Upload total(current)Completed sinceQuit
%s%3.2lf%% (%d/%d/%d)%llu (%d)%llu (%d)%sQuit
\n

\n"); } linebuffer_printf(lbuf, "\n"); linebuffer_writefd(lbuf, con->fd); linebuffer_free(lbuf); return 0; } 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; if (parse_sockaddr(con->req_args[1] +7, &addr) == 0) { struct torrent_file *torrent; list_for_each_entry(torrent, &torrent_list, list) { struct client_con *search; list_for_each_entry(search, &torrent->client_list, list) { if (!same_sockaddr(&search->addr, &addr)) continue; write(event_get_fd(search->event), "CTQUIT\n", 7); } } } } char *text = "HTTP/1.0 302 OK\r\nContent-Type: text/html\r\nConnection: close\r\nLocation: /\r\n\r\n"; write(con->fd, text, strlen(text)); return 0; }