#include #include #include #include #include #include "event.h" #include "httpd.h" #include "linebuffer.h" #include "list.h" #include "logging.h" #include "sockaddr.h" #include "tcpsocket.h" static LIST_HEAD(torrent_list); struct torrent_file { struct list_head list; struct list_head client_list; char *name; }; struct client_con { struct list_head list; struct sockaddr_in addr; struct event_fd *event; struct linebuffer *lbuf; int bw_up; int bw_dn; unsigned long long total_up; unsigned long long total_dn; int chunk_total; int chunk_avail; int chunk_have; long completed; struct torrent_file *torrent; }; static struct torrent_file * find_create_torrent(const char *filename) { struct torrent_file *torrent; list_for_each_entry(torrent, &torrent_list, list) { if (strcmp(torrent->name, filename) == 0) return torrent; } torrent = malloc(sizeof(struct torrent_file) + strlen(filename)); if (torrent == NULL) { log_print(LOG_WARN, "find_create_torrent(): out of memory"); return NULL; } INIT_LIST_HEAD(&torrent->client_list); torrent->name = strdup(filename); 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 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; if (linebuffer_readfd(con->lbuf, fd) < 0) { free_client(con); return -1; } char *line; while ((line = linebuffer_getline(con->lbuf, NULL)) != NULL) { 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; } } 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; } } 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); } /* move completed clients to top of the list, ordered by their timestamp */ if (con->chunk_have == con->chunk_total && con->chunk_total != 0 && con->completed == 0) { con->completed = time(NULL); list_del(&con->list); 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) - 300; struct torrent_file *torrent; list_for_each_entry(torrent, &torrent_list, list) { int delete = 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; } /* delete holds the number of clients to quit */ 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); 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; }