2007-07-14 15:31:27 +02:00
|
|
|
/***************************************************************************
|
|
|
|
* 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. *
|
|
|
|
***************************************************************************/
|
2007-06-18 15:44:05 +02:00
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <string.h>
|
2009-02-25 14:17:23 +01:00
|
|
|
|
2007-06-20 16:08:38 +02:00
|
|
|
#include <time.h>
|
2009-02-25 14:17:23 +01:00
|
|
|
#include <sys/types.h>
|
|
|
|
#include <sys/stat.h>
|
2007-06-18 15:44:05 +02:00
|
|
|
|
2009-02-25 14:17:23 +01:00
|
|
|
#include "configfile.h"
|
2007-06-18 15:44:05 +02:00
|
|
|
#include "event.h"
|
|
|
|
#include "httpd.h"
|
|
|
|
#include "linebuffer.h"
|
|
|
|
#include "list.h"
|
|
|
|
#include "logging.h"
|
|
|
|
#include "sockaddr.h"
|
|
|
|
#include "tcpsocket.h"
|
2009-05-03 14:20:03 +02:00
|
|
|
#include "torrentfile.h"
|
2007-06-18 15:44:05 +02:00
|
|
|
|
2009-05-03 14:20:03 +02:00
|
|
|
struct client_stats {
|
2009-02-24 12:26:37 +01:00
|
|
|
/* current bandwidth up/down */
|
2007-06-18 15:44:05 +02:00
|
|
|
int bw_up;
|
|
|
|
int bw_dn;
|
|
|
|
|
2009-02-24 12:26:37 +01:00
|
|
|
/* total bytes up/down */
|
2007-06-18 15:44:05 +02:00
|
|
|
unsigned long long total_up;
|
|
|
|
unsigned long long total_dn;
|
|
|
|
|
2009-02-24 12:26:37 +01:00
|
|
|
/* clients cloud view */
|
2007-06-18 15:44:05 +02:00
|
|
|
int chunk_total;
|
|
|
|
int chunk_avail;
|
|
|
|
int chunk_have;
|
|
|
|
};
|
|
|
|
|
2009-05-03 14:20:03 +02:00
|
|
|
struct client_con {
|
|
|
|
struct list_head list;
|
2007-07-07 13:11:51 +02:00
|
|
|
struct torrent_file *torrent;
|
2009-02-25 14:17:23 +01:00
|
|
|
|
2009-05-03 14:20:03 +02:00
|
|
|
struct sockaddr_in addr;
|
|
|
|
struct event_fd *event;
|
|
|
|
struct linebuffer *lbuf;
|
2009-02-25 14:17:23 +01:00
|
|
|
|
2009-05-03 14:20:03 +02:00
|
|
|
/* download stats */
|
|
|
|
struct client_stats stats;
|
2009-02-25 14:17:23 +01:00
|
|
|
|
2009-05-03 14:20:03 +02:00
|
|
|
/* timestamp when this client completed */
|
|
|
|
long completed;
|
|
|
|
};
|
2009-02-25 14:17:23 +01:00
|
|
|
|
2009-05-03 14:20:03 +02:00
|
|
|
static struct event_fd *listen_event;
|
|
|
|
static struct event_fd *httpd_event;
|
|
|
|
static struct event_timeout *timeout_event;
|
2009-02-25 14:17:23 +01:00
|
|
|
|
2007-06-18 15:44:05 +02:00
|
|
|
static void free_client(struct client_con *con)
|
|
|
|
{
|
2007-07-07 13:11:51 +02:00
|
|
|
list_del(&con->list);
|
|
|
|
|
|
|
|
/* remove torrents without clients */
|
2009-05-03 14:20:03 +02:00
|
|
|
if (list_empty(&con->torrent->client_list))
|
|
|
|
destroy_torrent(con->torrent);
|
2007-07-07 13:11:51 +02:00
|
|
|
|
2007-06-18 15:44:05 +02:00
|
|
|
close(event_get_fd(con->event));
|
|
|
|
event_remove_fd(con->event);
|
|
|
|
free(con->lbuf);
|
|
|
|
free(con);
|
|
|
|
}
|
|
|
|
|
2009-05-03 14:20:03 +02:00
|
|
|
static int ctcs_data_handler(int fd, void *privdata)
|
2007-06-18 15:44:05 +02:00
|
|
|
{
|
|
|
|
struct client_con *con = (struct client_con *)privdata;
|
2009-05-03 14:20:03 +02:00
|
|
|
struct client_stats *stats = &con->stats;
|
2007-06-18 15:44:05 +02:00
|
|
|
|
2009-02-24 12:26:37 +01:00
|
|
|
/* get data from socket */
|
2007-06-19 15:20:31 +02:00
|
|
|
if (linebuffer_readfd(con->lbuf, fd) < 0) {
|
2007-06-18 15:44:05 +02:00
|
|
|
free_client(con);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
char *line;
|
2007-06-19 15:20:31 +02:00
|
|
|
while ((line = linebuffer_getline(con->lbuf, NULL)) != NULL) {
|
2009-02-24 12:26:37 +01:00
|
|
|
/* bandwidth update */
|
2007-06-18 15:44:05 +02:00
|
|
|
if (strncmp(line, "CTBW ", 5) == 0) {
|
|
|
|
int bwup, bwdn, liup, lidn;
|
|
|
|
if (sscanf(line +5, "%d,%d %d,%d", &bwdn, &bwup, &lidn, &liup) == 4) {
|
2009-05-03 14:20:03 +02:00
|
|
|
stats->bw_up = bwup;
|
|
|
|
stats->bw_dn = bwdn;
|
2007-06-18 15:44:05 +02:00
|
|
|
}
|
|
|
|
|
2009-02-24 12:49:40 +01:00
|
|
|
/* status update (assume protocol v2) */
|
2007-06-18 15:44:05 +02:00
|
|
|
} 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) {
|
|
|
|
|
2009-05-03 14:20:03 +02:00
|
|
|
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;
|
2007-06-18 15:44:05 +02:00
|
|
|
}
|
2007-07-07 13:11:51 +02:00
|
|
|
|
2009-02-24 12:26:37 +01:00
|
|
|
/* update torrent-file */
|
2007-07-07 13:11:51 +02:00
|
|
|
} else if (strncmp(line, "CTORRENT ", 9) == 0) {
|
|
|
|
char *filename = strrchr(line +9, ' ');
|
2009-05-03 14:20:03 +02:00
|
|
|
if (filename != NULL && strcmp(filename, con->torrent->name) != 0) {
|
2007-07-07 13:11:51 +02:00
|
|
|
struct torrent_file *torrent = find_create_torrent(filename +1);
|
|
|
|
if (torrent != NULL) {
|
|
|
|
list_del(&con->list);
|
2009-05-03 14:20:03 +02:00
|
|
|
list_add_tail(&con->list, &torrent->client_list);
|
2007-07-07 13:11:51 +02:00
|
|
|
con->torrent = torrent;
|
|
|
|
}
|
|
|
|
}
|
2007-06-18 15:44:05 +02:00
|
|
|
}
|
2007-06-19 15:20:31 +02:00
|
|
|
linebuffer_freeline(con->lbuf);
|
2007-06-18 15:44:05 +02:00
|
|
|
}
|
2007-06-20 16:08:38 +02:00
|
|
|
|
2009-02-24 12:26:37 +01:00
|
|
|
/* client completed? */
|
2009-05-03 14:20:03 +02:00
|
|
|
if (stats->chunk_have == stats->chunk_total && stats->chunk_total != 0 && con->completed == 0) {
|
2007-06-20 16:08:38 +02:00
|
|
|
con->completed = time(NULL);
|
|
|
|
|
|
|
|
list_del(&con->list);
|
|
|
|
|
2009-02-24 12:26:37 +01:00
|
|
|
/* sorted insert, completed on top of list, ordered by complete-timestamp */
|
2007-06-20 16:08:38 +02:00
|
|
|
struct client_con *search;
|
2007-07-07 13:11:51 +02:00
|
|
|
list_for_each_entry(search, &con->torrent->client_list, list) {
|
2007-06-20 16:08:38 +02:00
|
|
|
if (search->completed == 0)
|
|
|
|
break;
|
|
|
|
|
|
|
|
if (search->completed > con->completed)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
list_add_tail(&con->list, &search->list);
|
|
|
|
return 0;
|
|
|
|
}
|
2007-06-18 15:44:05 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2009-05-03 14:20:03 +02:00
|
|
|
static int ctcs_trigger_status(void *privdata)
|
2007-06-19 15:44:57 +02:00
|
|
|
{
|
2009-02-24 12:49:40 +01:00
|
|
|
long timeout = time(NULL) - (int)privdata;
|
2007-06-20 16:08:38 +02:00
|
|
|
|
2007-07-07 13:11:51 +02:00
|
|
|
struct torrent_file *torrent;
|
|
|
|
list_for_each_entry(torrent, &torrent_list, list) {
|
2009-02-25 14:17:23 +01:00
|
|
|
int seeder = 0;
|
|
|
|
int leecher = 0;
|
2007-06-19 15:44:57 +02:00
|
|
|
|
2007-07-07 13:11:51 +02:00
|
|
|
struct client_con *con;
|
|
|
|
list_for_each_entry(con, &torrent->client_list, list) {
|
|
|
|
write(event_get_fd(con->event), "SENDSTATUS\n", 11);
|
2007-06-20 16:08:38 +02:00
|
|
|
|
2009-02-25 14:17:23 +01:00
|
|
|
if (con->completed)
|
|
|
|
seeder++;
|
|
|
|
else
|
|
|
|
leecher++;
|
2007-07-07 13:11:51 +02:00
|
|
|
}
|
2007-06-20 16:08:38 +02:00
|
|
|
|
2009-02-25 14:17:23 +01:00
|
|
|
/* no seeders available and no local seeder running => spawn one */
|
|
|
|
if (leecher > 0 && seeder == 0 && torrent->child == NULL)
|
2009-05-03 14:20:03 +02:00
|
|
|
seed_torrent(torrent);
|
2009-02-25 14:17:23 +01:00
|
|
|
|
2007-07-07 13:11:51 +02:00
|
|
|
/* delete holds the number of clients to quit */
|
2009-02-25 14:17:23 +01:00
|
|
|
int delete = seeder - leecher;
|
2007-07-07 13:11:51 +02:00
|
|
|
list_for_each_entry(con, &torrent->client_list, list) {
|
|
|
|
if (delete <= 0)
|
|
|
|
break;
|
2007-06-20 16:08:38 +02:00
|
|
|
|
2007-07-07 13:11:51 +02:00
|
|
|
if (con->completed == 0 || con->completed > timeout)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
write(event_get_fd(con->event), "CTQUIT\n", 7);
|
|
|
|
delete--;
|
|
|
|
}
|
2007-06-20 16:08:38 +02:00
|
|
|
}
|
2007-06-19 15:44:57 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2009-05-03 14:20:03 +02:00
|
|
|
static int ctcs_accept_handler(int fd, void *privdata)
|
2007-06-18 15:44:05 +02:00
|
|
|
{
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2009-05-03 14:20:03 +02:00
|
|
|
int sockfd = tcp_accept(fd, &con->addr);
|
2007-06-18 15:44:05 +02:00
|
|
|
if (sockfd < 0) {
|
2009-05-03 14:20:03 +02:00
|
|
|
log_print(LOG_WARN, "accept_cb(): tcp_accept()");
|
2007-06-18 15:44:05 +02:00
|
|
|
free(con->lbuf);
|
|
|
|
free(con);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2009-05-03 14:20:03 +02:00
|
|
|
con->event = event_add_readfd(NULL, sockfd, ctcs_data_handler, con);
|
2007-06-18 15:44:05 +02:00
|
|
|
|
2009-02-24 12:26:37 +01:00
|
|
|
/* assign default torrent */
|
2007-07-07 13:11:51 +02:00
|
|
|
con->torrent = find_create_torrent("[unknown]");
|
|
|
|
list_add_tail(&con->list, &con->torrent->client_list);
|
2007-06-18 15:44:05 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2009-05-03 14:20:03 +02:00
|
|
|
static int ctcs_httpd_show(struct httpd_con *con, void *privdata)
|
2007-06-18 15:44:05 +02:00
|
|
|
{
|
2007-07-07 13:11:51 +02:00
|
|
|
struct linebuffer *lbuf = create_linebuffer(16384);
|
2007-06-19 15:44:57 +02:00
|
|
|
if (lbuf == NULL) {
|
2007-06-18 15:44:05 +02:00
|
|
|
httpd_send_error(con, "500 ERROR", "Out of Memory");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2007-06-20 16:08:38 +02:00
|
|
|
linebuffer_printf(lbuf, "HTTP/1.0 200 OK\r\nContent-Type: text/html\r\nConnection: close\r\n\r\n");
|
2007-07-07 13:11:51 +02:00
|
|
|
linebuffer_printf(lbuf, "<html><head><meta http-equiv=\"refresh\" content=\"30;\"></head><body><h1>ctorrent stats</h1>\n");
|
|
|
|
|
|
|
|
struct torrent_file *torrent;
|
|
|
|
list_for_each_entry(torrent, &torrent_list, list) {
|
|
|
|
if (list_empty(&torrent->client_list))
|
|
|
|
continue;
|
2007-06-18 15:44:05 +02:00
|
|
|
|
2007-07-07 13:53:36 +02:00
|
|
|
linebuffer_printf(lbuf, "<table border=\"1\">\n<tr><td colspan=\"6\" align=\"center\">%s</td></tr>\n", torrent->name);
|
|
|
|
linebuffer_printf(lbuf, "<tr></td><td><b>Client IP:Port</b></td><td><b>Chunks (have/total/avail)</b></td><td><b>Download total(current)</b></td>");
|
|
|
|
linebuffer_printf(lbuf, "<td><b>Upload total(current)</b></td><td><b>Completed since</b></td><td><b>Quit</b></td></tr>\n");
|
2007-07-07 13:11:51 +02:00
|
|
|
|
|
|
|
struct client_con *tmp;
|
|
|
|
list_for_each_entry(tmp, &torrent->client_list, list) {
|
2009-05-03 14:20:03 +02:00
|
|
|
struct client_stats *stats = &tmp->stats;
|
2007-07-07 13:11:51 +02:00
|
|
|
linebuffer_printf(lbuf, "<tr><td align=\"right\">%s</td><td align=\"right\">%3.2lf%% (%d/%d/%d)</td>",
|
2007-06-18 15:44:05 +02:00
|
|
|
get_sockaddr_buf(&tmp->addr),
|
2009-05-03 14:20:03 +02:00
|
|
|
(double)stats->chunk_have / (double)stats->chunk_total * 100.0,
|
|
|
|
stats->chunk_have,
|
|
|
|
stats->chunk_total,
|
|
|
|
stats->chunk_avail);
|
2007-07-07 13:11:51 +02:00
|
|
|
|
2007-07-07 13:53:36 +02:00
|
|
|
linebuffer_printf(lbuf, "<td align=\"right\">%llu (%d)</td><td align=\"right\">%llu (%d)</td><td align=\"right\">%s</td>",
|
2009-05-03 14:20:03 +02:00
|
|
|
stats->total_dn,
|
|
|
|
stats->bw_dn,
|
|
|
|
stats->total_up,
|
|
|
|
stats->bw_up,
|
2007-06-20 16:08:38 +02:00
|
|
|
(tmp->completed != 0) ? ctime(&tmp->completed) : "-");
|
2007-07-07 13:53:36 +02:00
|
|
|
|
|
|
|
linebuffer_printf(lbuf, "<td><a href=\"/quit?client=%s\">Quit</td></tr>\n", get_sockaddr_buf(&tmp->addr));
|
2007-07-07 13:11:51 +02:00
|
|
|
}
|
|
|
|
linebuffer_printf(lbuf, "</table>\n<br><br>\n");
|
2007-06-18 15:44:05 +02:00
|
|
|
}
|
2007-07-07 13:11:51 +02:00
|
|
|
linebuffer_printf(lbuf, "</body></html>\n");
|
2007-06-18 15:44:05 +02:00
|
|
|
|
2007-06-19 15:44:57 +02:00
|
|
|
linebuffer_writefd(lbuf, con->fd);
|
|
|
|
linebuffer_free(lbuf);
|
2007-06-18 15:44:05 +02:00
|
|
|
return 0;
|
|
|
|
}
|
2007-07-07 13:53:36 +02:00
|
|
|
|
2009-05-03 14:20:03 +02:00
|
|
|
static int ctcs_httpd_quit(struct httpd_con *con, void *privdata)
|
2007-07-07 13:53:36 +02:00
|
|
|
{
|
|
|
|
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) {
|
2007-07-14 15:26:31 +02:00
|
|
|
if (!same_sockaddr(&search->addr, &addr))
|
2007-07-07 13:53:36 +02:00
|
|
|
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;
|
|
|
|
}
|
2009-05-03 14:20:03 +02:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|