summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDirk Engling <erdgeist@erdgeist.org>2026-10-26 22:32:52 +0100
committerDirk Engling <erdgeist@erdgeist.org>2026-10-26 22:32:52 +0100
commit1f9494979268b62ed4b4588fb7d9e790c0e12100 (patch)
treeed4f78369fe610b5c9188cb4159a23370d50a799
parent6d5485b97cd46f81512e9198ac7c59c7ba1d9247 (diff)
replace all calls to sprintf with a format helper that advances a cursor. Also convert the meta-formatter (i.e. results printer) to this scheme
-rw-r--r--Makefile8
-rw-r--r--ot_config.c7
-rw-r--r--ot_fmt.c42
-rw-r--r--ot_fmt.h21
-rw-r--r--ot_fullscrape.c38
-rw-r--r--ot_http.c40
-rw-r--r--ot_stats.c302
-rw-r--r--ot_stats.h4
-rw-r--r--ot_sync.c13
-rw-r--r--tests/runner.c1
-rw-r--r--tests/t_fmt.c108
-rw-r--r--tests/test.h1
-rw-r--r--trackerlogic.c55
-rw-r--r--trackerlogic.h2
14 files changed, 418 insertions, 224 deletions
diff --git a/Makefile b/Makefile
index d74742a..a884c2c 100644
--- a/Makefile
+++ b/Makefile
@@ -69,8 +69,8 @@ CFLAGS_libowfat=-I$(LIBOWFAT_HEADERS)
69LDFLAGS_libowfat=-L$(LIBOWFAT_LIBRARY) -lowfat 69LDFLAGS_libowfat=-L$(LIBOWFAT_LIBRARY) -lowfat
70 70
71BINARY =opentracker 71BINARY =opentracker
72HEADERS=trackerlogic.h ot_urlencoded.h ot_batch.h ot_buf.h ot_event.h ot_fdstate.h ot_hex.h ot_ip.h ot_config.h ot_scan.h ot_socket.h ot_mutex.h ot_random.h ot_stats.h ot_vector.h ot_clean.h ot_udp.h ot_iovec.h ot_fullscrape.h ot_accesslist.h ot_http.h ot_livesync.h ot_rijndael.h 72HEADERS=trackerlogic.h ot_urlencoded.h ot_batch.h ot_buf.h ot_event.h ot_fdstate.h ot_fmt.h ot_hex.h ot_ip.h ot_config.h ot_scan.h ot_socket.h ot_mutex.h ot_random.h ot_stats.h ot_vector.h ot_clean.h ot_udp.h ot_iovec.h ot_fullscrape.h ot_accesslist.h ot_http.h ot_livesync.h ot_rijndael.h
73SOURCES=opentracker.c trackerlogic.c ot_urlencoded.c ot_batch.c ot_buf.c ot_event.c ot_fdstate.c ot_hex.c ot_ip.c ot_config.c ot_scan.c ot_socket.c ot_mutex.c ot_random.c ot_stats.c ot_vector.c ot_clean.c ot_udp.c ot_iovec.c ot_fullscrape.c ot_accesslist.c ot_http.c ot_livesync.c ot_rijndael.c 73SOURCES=opentracker.c trackerlogic.c ot_urlencoded.c ot_batch.c ot_buf.c ot_event.c ot_fdstate.c ot_fmt.c ot_hex.c ot_ip.c ot_config.c ot_scan.c ot_socket.c ot_mutex.c ot_random.c ot_stats.c ot_vector.c ot_clean.c ot_udp.c ot_iovec.c ot_fullscrape.c ot_accesslist.c ot_http.c ot_livesync.c ot_rijndael.c
74SOURCES_proxy=proxy.c ot_batch.c ot_fdstate.c ot_vector.c ot_mutex.c ot_random.c ot_scan.c ot_ip.c ot_socket.c 74SOURCES_proxy=proxy.c ot_batch.c ot_fdstate.c ot_vector.c ot_mutex.c ot_random.c ot_scan.c ot_ip.c ot_socket.c
75 75
76OBJECTS = $(SOURCES:%.c=%.o) 76OBJECTS = $(SOURCES:%.c=%.o)
@@ -120,8 +120,8 @@ $(OBJECTS_proxy_debug): $(HEADERS)
120# has to. tests/stubs.c supplies the few symbols those modules reference but 120# has to. tests/stubs.c supplies the few symbols those modules reference but
121# the tests never reach. 121# the tests never reach.
122TESTBIN=tests/runner 122TESTBIN=tests/runner
123TEST_SOURCES=tests/runner.c tests/test.c tests/stubs.c tests/t_urlencoded.c tests/t_iovec.c tests/t_vector.c tests/t_random.c tests/t_hex.c tests/t_accesslist.c tests/t_scan.c tests/t_buf.c tests/t_ip.c tests/t_socket.c tests/t_mutex.c tests/t_batch.c tests/t_fdstate.c tests/t_event.c 123TEST_SOURCES=tests/runner.c tests/test.c tests/stubs.c tests/t_urlencoded.c tests/t_iovec.c tests/t_vector.c tests/t_random.c tests/t_hex.c tests/t_accesslist.c tests/t_scan.c tests/t_buf.c tests/t_ip.c tests/t_socket.c tests/t_mutex.c tests/t_batch.c tests/t_fdstate.c tests/t_event.c tests/t_fmt.c
124TEST_UNITS=ot_urlencoded.c ot_event.c ot_batch.c ot_fdstate.c ot_buf.c ot_ip.c ot_socket.c ot_iovec.c ot_vector.c ot_random.c ot_hex.c ot_scan.c ot_accesslist.c ot_mutex.c 124TEST_UNITS=ot_urlencoded.c ot_fmt.c ot_event.c ot_batch.c ot_fdstate.c ot_buf.c ot_ip.c ot_socket.c ot_iovec.c ot_vector.c ot_random.c ot_hex.c ot_scan.c ot_accesslist.c ot_mutex.c
125TEST_HEADERS=tests/test.h $(HEADERS) 125TEST_HEADERS=tests/test.h $(HEADERS)
126# Tests reach code that sits behind feature flags, so the units under test are 126# Tests reach code that sits behind feature flags, so the units under test are
127# built with those features enabled. A test binary is not a production binary. 127# built with those features enabled. A test binary is not a production binary.
diff --git a/ot_config.c b/ot_config.c
index 32625bd..ee6bc12 100644
--- a/ot_config.c
+++ b/ot_config.c
@@ -544,8 +544,11 @@ void config_parse(int argc, char **argv) {
544 help(argv[0]); 544 help(argv[0]);
545 exit(0); 545 exit(0);
546 case 'v': { 546 case 'v': {
547 char buffer[8192]; 547 char buffer[8192];
548 stats_return_tracker_version(buffer); 548 char *cursor = buffer;
549
550 stats_return_tracker_version(&cursor, buffer + sizeof(buffer) - 1);
551 *cursor = 0;
549 fputs(buffer, stderr); 552 fputs(buffer, stderr);
550 exit(0); 553 exit(0);
551 } 554 }
diff --git a/ot_fmt.c b/ot_fmt.c
new file mode 100644
index 0000000..9cab25f
--- /dev/null
+++ b/ot_fmt.c
@@ -0,0 +1,42 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever.
3
4 $id$ */
5
6/* System */
7#include <stdarg.h>
8#include <stdio.h>
9#include <string.h>
10
11/* Opentracker */
12#include "ot_fmt.h"
13
14void fmt_append(char **cursor, const char *end, const char *format, ...) {
15 va_list arguments;
16 size_t room;
17 int written;
18
19 if (*cursor >= end) {
20 *cursor = (char *)end;
21 return;
22 }
23
24 room = (size_t)(end - *cursor);
25 va_start(arguments, format);
26 written = vsnprintf(*cursor, room, format, arguments);
27 va_end(arguments);
28
29 if (written < 0)
30 return;
31 *cursor = (size_t)written >= room ? (char *)end : *cursor + written;
32}
33
34void fmt_bytes(char **cursor, const char *end, const void *bytes, size_t length) {
35 if (*cursor >= end || (size_t)(end - *cursor) < length) {
36 *cursor = (char *)end;
37 return;
38 }
39
40 memcpy(*cursor, bytes, length);
41 *cursor += length;
42}
diff --git a/ot_fmt.h b/ot_fmt.h
new file mode 100644
index 0000000..851b2d8
--- /dev/null
+++ b/ot_fmt.h
@@ -0,0 +1,21 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever.
3
4 $id$ */
5
6#ifndef OT_FMT_H__
7#define OT_FMT_H__
8
9#include <stddef.h>
10
11/* Appending to a cursor that walks a fixed buffer. Both take the cursor by
12 reference and advance it, so a reply is written as a run of statements and
13 no caller does pointer arithmetic of its own. */
14
15void fmt_append(char **cursor, const char *end, const char *format, ...) __attribute__((format(printf, 3, 4)));
16
17/* Append raw bytes for the binary stretches of a bencoded reply.
18 Either everything is appended or nothing at all */
19void fmt_bytes(char **cursor, const char *end, const void *bytes, size_t length);
20
21#endif
diff --git a/ot_fullscrape.c b/ot_fullscrape.c
index 2ba3b24..8518bd2 100644
--- a/ot_fullscrape.c
+++ b/ot_fullscrape.c
@@ -18,6 +18,7 @@
18#endif 18#endif
19 19
20/* Opentracker */ 20/* Opentracker */
21#include "ot_fmt.h"
21#include "ot_hex.h" 22#include "ot_hex.h"
22#include "ot_fullscrape.h" 23#include "ot_fullscrape.h"
23#include "ot_iovec.h" 24#include "ot_iovec.h"
@@ -83,10 +84,15 @@ void fullscrape_deliver( int64_t sock, ot_tasktype tasktype ) {
83 mutex_workqueue_pushtask( sock, TASK_FULLSCRAPE | tasktype ); 84 mutex_workqueue_pushtask( sock, TASK_FULLSCRAPE | tasktype );
84} 85}
85 86
86static char * fullscrape_write_one( ot_tasktype mode, char *r, ot_torrent *torrent, ot_hash *hash ) { 87static void fullscrape_write_one( ot_tasktype mode, char **cursor, ot_torrent *torrent, ot_hash *hash ) {
87 size_t seed_count = torrent->peer_list6->seed_count + torrent->peer_list4->seed_count; 88 /* Callers guarantee this much room: the chunk writer keeps its low
88 size_t peer_count = torrent->peer_list6->peer_count + torrent->peer_list4->peer_count; 89 watermark that far from the end, the compressors hand over a buffer of
89 size_t down_count = torrent->peer_list6->down_count + torrent->peer_list4->down_count; 90 exactly that size */
91 char *r = *cursor;
92 const char *r_end = r + OT_SCRAPE_MAXENTRYLEN;
93 size_t seed_count = torrent->peer_list6->seed_count + torrent->peer_list4->seed_count;
94 size_t peer_count = torrent->peer_list6->peer_count + torrent->peer_list4->peer_count;
95 size_t down_count = torrent->peer_list6->down_count + torrent->peer_list4->down_count;
90 96
91 switch (mode & TASK_TASK_MASK) { 97 switch (mode & TASK_TASK_MASK) {
92 case TASK_FULLSCRAPE: 98 case TASK_FULLSCRAPE:
@@ -98,18 +104,18 @@ static char * fullscrape_write_one( ot_tasktype mode, char *r, ot_torrent *torre
98 memcpy(r, hash, sizeof(ot_hash)); 104 memcpy(r, hash, sizeof(ot_hash));
99 r += sizeof(ot_hash); 105 r += sizeof(ot_hash);
100 /* push rest of the scrape string */ 106 /* push rest of the scrape string */
101 r += sprintf(r, "d8:completei%zde10:downloadedi%zde10:incompletei%zdee", seed_count, down_count, peer_count - seed_count); 107 fmt_append(&r, r_end, "d8:completei%zde10:downloadedi%zde10:incompletei%zdee", seed_count, down_count, peer_count - seed_count);
102 108
103 break; 109 break;
104 case TASK_FULLSCRAPE_TPB_ASCII: 110 case TASK_FULLSCRAPE_TPB_ASCII:
105 hex_from_hash(r, *hash); 111 hex_from_hash(r, *hash);
106 r += 2 * sizeof(ot_hash); 112 r += 2 * sizeof(ot_hash);
107 r += sprintf(r, ":%zd:%zd\n", seed_count, peer_count - seed_count); 113 fmt_append(&r, r_end, ":%zd:%zd\n", seed_count, peer_count - seed_count);
108 break; 114 break;
109 case TASK_FULLSCRAPE_TPB_ASCII_PLUS: 115 case TASK_FULLSCRAPE_TPB_ASCII_PLUS:
110 hex_from_hash(r, *hash); 116 hex_from_hash(r, *hash);
111 r += 2 * sizeof(ot_hash); 117 r += 2 * sizeof(ot_hash);
112 r += sprintf(r, ":%zd:%zd:%zd\n", seed_count, peer_count - seed_count, down_count); 118 fmt_append(&r, r_end, ":%zd:%zd:%zd\n", seed_count, peer_count - seed_count, down_count);
113 break; 119 break;
114 case TASK_FULLSCRAPE_TPB_BINARY: 120 case TASK_FULLSCRAPE_TPB_BINARY:
115 memcpy(r, *hash, sizeof(ot_hash)); 121 memcpy(r, *hash, sizeof(ot_hash));
@@ -120,15 +126,15 @@ static char * fullscrape_write_one( ot_tasktype mode, char *r, ot_torrent *torre
120 break; 126 break;
121 case TASK_FULLSCRAPE_TPB_URLENCODED: 127 case TASK_FULLSCRAPE_TPB_URLENCODED:
122 r += urlencoded_format(r, *hash, sizeof(ot_hash)); 128 r += urlencoded_format(r, *hash, sizeof(ot_hash));
123 r += sprintf(r, ":%zd:%zd\n", seed_count, peer_count - seed_count); 129 fmt_append(&r, r_end, ":%zd:%zd\n", seed_count, peer_count - seed_count);
124 break; 130 break;
125 case TASK_FULLSCRAPE_TRACKERSTATE: 131 case TASK_FULLSCRAPE_TRACKERSTATE:
126 hex_from_hash(r, *hash); 132 hex_from_hash(r, *hash);
127 r += 2 * sizeof(ot_hash); 133 r += 2 * sizeof(ot_hash);
128 r += sprintf(r, ":%zd:%zd\n", torrent->peer_list6->base, down_count); 134 fmt_append(&r, r_end, ":%zd:%zd\n", torrent->peer_list6->base, down_count);
129 break; 135 break;
130 } 136 }
131 return r; 137 *cursor = r;
132} 138}
133 139
134static void fullscrape_make(int taskid, ot_tasktype mode) { 140static void fullscrape_make(int taskid, ot_tasktype mode) {
@@ -145,7 +151,7 @@ static void fullscrape_make(int taskid, ot_tasktype mode) {
145 re = r + OT_SCRAPE_CHUNK_SIZE - OT_SCRAPE_MAXENTRYLEN; 151 re = r + OT_SCRAPE_CHUNK_SIZE - OT_SCRAPE_MAXENTRYLEN;
146 152
147 if ((mode & TASK_TASK_MASK) == TASK_FULLSCRAPE) 153 if ((mode & TASK_TASK_MASK) == TASK_FULLSCRAPE)
148 r += sprintf(r, "d5:filesd"); 154 fmt_append(&r, (char *)iovector.iov_base + OT_SCRAPE_CHUNK_SIZE, "d5:filesd");
149 155
150 /* For each bucket... */ 156 /* For each bucket... */
151 for (bucket = 0; bucket < OT_BUCKET_COUNT; ++bucket) { 157 for (bucket = 0; bucket < OT_BUCKET_COUNT; ++bucket) {
@@ -156,7 +162,7 @@ static void fullscrape_make(int taskid, ot_tasktype mode) {
156 162
157 /* For each torrent in this bucket.. */ 163 /* For each torrent in this bucket.. */
158 for (i = 0; i < torrents_list->size; ++i) { 164 for (i = 0; i < torrents_list->size; ++i) {
159 r = fullscrape_write_one(mode, r, torrents + i, &torrents[i].hash); 165 fullscrape_write_one(mode, &r, torrents + i, &torrents[i].hash);
160 166
161 if (r > re) { 167 if (r > re) {
162 iovector.iov_len = r - (char *)iovector.iov_base; 168 iovector.iov_len = r - (char *)iovector.iov_base;
@@ -187,7 +193,7 @@ static void fullscrape_make(int taskid, ot_tasktype mode) {
187 } 193 }
188 194
189 if ((mode & TASK_TASK_MASK) == TASK_FULLSCRAPE) 195 if ((mode & TASK_TASK_MASK) == TASK_FULLSCRAPE)
190 r += sprintf(r, "ee"); 196 fmt_append(&r, (char *)iovector.iov_base + OT_SCRAPE_CHUNK_SIZE, "ee");
191 197
192 /* Send rest of data */ 198 /* Send rest of data */
193 iovector.iov_len = r - (char *)iovector.iov_base; 199 iovector.iov_len = r - (char *)iovector.iov_base;
@@ -230,7 +236,8 @@ static void fullscrape_make_gzip(int taskid, ot_tasktype mode) {
230 /* For each torrent in this bucket.. */ 236 /* For each torrent in this bucket.. */
231 for (i = 0; i < torrents_list->size; ++i) { 237 for (i = 0; i < torrents_list->size; ++i) {
232 char compress_buffer[OT_SCRAPE_MAXENTRYLEN]; 238 char compress_buffer[OT_SCRAPE_MAXENTRYLEN];
233 r = fullscrape_write_one(mode, compress_buffer, torrents + i, &torrents[i].hash); 239 r = compress_buffer;
240 fullscrape_write_one(mode, &r, torrents + i, &torrents[i].hash);
234 strm.next_in = (uint8_t *)compress_buffer; 241 strm.next_in = (uint8_t *)compress_buffer;
235 strm.avail_in = r - compress_buffer; 242 strm.avail_in = r - compress_buffer;
236 zres = deflate(&strm, Z_NO_FLUSH); 243 zres = deflate(&strm, Z_NO_FLUSH);
@@ -359,7 +366,8 @@ static void fullscrape_make_zstd(int taskid, ot_tasktype mode) {
359 /* For each torrent in this bucket.. */ 366 /* For each torrent in this bucket.. */
360 for (i = 0; i < torrents_list->size; ++i) { 367 for (i = 0; i < torrents_list->size; ++i) {
361 char compress_buffer[OT_SCRAPE_MAXENTRYLEN]; 368 char compress_buffer[OT_SCRAPE_MAXENTRYLEN];
362 r = fullscrape_write_one(mode, compress_buffer, torrents + i, &torrents[i].hash); 369 r = compress_buffer;
370 fullscrape_write_one(mode, &r, torrents + i, &torrents[i].hash);
363 inbuf.src = compress_buffer; 371 inbuf.src = compress_buffer;
364 inbuf.size = r - compress_buffer; 372 inbuf.size = r - compress_buffer;
365 inbuf.pos = 0; 373 inbuf.pos = 0;
diff --git a/ot_http.c b/ot_http.c
index e1798dc..36819c7 100644
--- a/ot_http.c
+++ b/ot_http.c
@@ -24,6 +24,7 @@
24#include "ot_scan.h" 24#include "ot_scan.h"
25#include "ot_event.h" 25#include "ot_event.h"
26#include "ot_fdstate.h" 26#include "ot_fdstate.h"
27#include "ot_fmt.h"
27#include "ot_stats.h" 28#include "ot_stats.h"
28#include "ot_urlencoded.h" 29#include "ot_urlencoded.h"
29#include "trackerlogic.h" 30#include "trackerlogic.h"
@@ -257,6 +258,7 @@ static ssize_t http_handle_stats(const int64_t sock, struct ot_workstruct *ws, c
257 {"txtp", TASK_FULLSCRAPE_TPB_ASCII_PLUS}, {NULL, -3}}; 258 {"txtp", TASK_FULLSCRAPE_TPB_ASCII_PLUS}, {NULL, -3}};
258 259
259 struct http_data *cookie = fdstate_cookie(sock); 260 struct http_data *cookie = fdstate_cookie(sock);
261 char *reply;
260 int mode = TASK_STATS_PEERS, scanon = 1, format = 0; 262 int mode = TASK_STATS_PEERS, scanon = 1, format = 0;
261 263
262 /* A socket without one is a dead socket, which is what http_sendiovecdata 264 /* A socket without one is a dead socket, which is what http_sendiovecdata
@@ -333,7 +335,9 @@ static ssize_t http_handle_stats(const int64_t sock, struct ot_workstruct *ws, c
333 } 335 }
334 336
335 /* Simple stats can be answerred immediately */ 337 /* Simple stats can be answerred immediately */
336 return ws->reply_size = return_stats_for_tracker(ws->reply, mode, 0); 338 reply = ws->reply;
339 return_stats_for_tracker(&reply, ws->outbuf + G_OUTBUF_SIZE, mode, 0);
340 return ws->reply_size = reply - ws->reply;
337} 341}
338 342
339#ifdef WANT_MODEST_FULLSCRAPES 343#ifdef WANT_MODEST_FULLSCRAPES
@@ -417,9 +421,10 @@ static ssize_t http_handle_fullscrape(const int64_t sock, struct ot_workstruct *
417static ssize_t http_handle_scrape(const int64_t sock, struct ot_workstruct *ws, char *read_ptr) { 421static ssize_t http_handle_scrape(const int64_t sock, struct ot_workstruct *ws, char *read_ptr) {
418 static const ot_keywords keywords_scrape[] = {{"info_hash", 1}, {NULL, -3}}; 422 static const ot_keywords keywords_scrape[] = {{"info_hash", 1}, {NULL, -3}};
419 423
420 ot_hash *multiscrape_buf = (ot_hash *)ws->request; 424 ot_hash *multiscrape_buf = (ot_hash *)ws->request;
421 int scanon = 1; 425 char *reply = ws->reply;
422 size_t numwant = 0; 426 int scanon = 1;
427 size_t numwant = 0;
423 428
424 /* This is to hack around stupid clients that send "scrape ?info_hash" */ 429 /* This is to hack around stupid clients that send "scrape ?info_hash" */
425 if (read_ptr[-1] != '?') { 430 if (read_ptr[-1] != '?') {
@@ -457,7 +462,8 @@ static ssize_t http_handle_scrape(const int64_t sock, struct ot_workstruct *ws,
457 numwant = OT_MAXMULTISCRAPE_COUNT; 462 numwant = OT_MAXMULTISCRAPE_COUNT;
458 463
459 /* Enough for http header + whole scrape string */ 464 /* Enough for http header + whole scrape string */
460 ws->reply_size = return_tcp_scrape_for_torrent((const ot_hash *)multiscrape_buf, numwant, ws->reply); 465 return_tcp_scrape_for_torrent((const ot_hash *)multiscrape_buf, numwant, &reply, ws->outbuf + G_OUTBUF_SIZE);
466 ws->reply_size = reply - ws->reply;
461 stats_issue_event(EVENT_SCRAPE, FLAG_TCP, ws->reply_size); 467 stats_issue_event(EVENT_SCRAPE, FLAG_TCP, ws->reply_size);
462 return ws->reply_size; 468 return ws->reply_size;
463} 469}
@@ -493,6 +499,16 @@ static ot_keywords keywords_announce[] = {{"port", 1}, {"left", 2}, {"event",
493#endif 499#endif
494 {"peer_id", 9}, {NULL, -3}}; 500 {"peer_id", 9}, {NULL, -3}};
495static ot_keywords keywords_announce_event[] = {{"completed", 1}, {"stopped", 2}, {NULL, -3}}; 501static ot_keywords keywords_announce_event[] = {{"completed", 1}, {"stopped", 2}, {NULL, -3}};
502
503/* For the replies that are one fixed string and nothing else, where the only
504 thing the caller wants back is how long it turned out */
505static ssize_t reply_literal(struct ot_workstruct *ws, const char *text) {
506 char *reply = ws->reply;
507
508 fmt_bytes(&reply, ws->outbuf + G_OUTBUF_SIZE, text, strlen(text));
509 return reply - ws->reply;
510}
511
496static ssize_t http_handle_announce(const int64_t sock, struct ot_workstruct *ws, char *read_ptr) { 512static ssize_t http_handle_announce(const int64_t sock, struct ot_workstruct *ws, char *read_ptr) {
497 size_t numwant; 513 size_t numwant;
498 int64_t tmp; 514 int64_t tmp;
@@ -621,7 +637,7 @@ static ssize_t http_handle_announce(const int64_t sock, struct ot_workstruct
621 HTTPERROR_400_PARAM; 637 HTTPERROR_400_PARAM;
622 if (*tmp_buf == '-') { 638 if (*tmp_buf == '-') {
623 loglist_reset(); 639 loglist_reset();
624 return ws->reply_size = sprintf(ws->reply, "Successfully removed.\n"); 640 return ws->reply_size = reply_literal(ws, "Successfully removed.\n");
625 } 641 }
626 parsed = ip_parse(tmp_buf, net.address); 642 parsed = ip_parse(tmp_buf, net.address);
627 if (!parsed) 643 if (!parsed)
@@ -638,7 +654,7 @@ static ssize_t http_handle_announce(const int64_t sock, struct ot_workstruct
638 } 654 }
639 net.bits = bits; 655 net.bits = bits;
640 loglist_add_network(&net); 656 loglist_add_network(&net);
641 return ws->reply_size = sprintf(ws->reply, "Successfully added.\n"); 657 return ws->reply_size = reply_literal(ws, "Successfully added.\n");
642 //} 658 //}
643 } break; 659 } break;
644#endif 660#endif
@@ -661,7 +677,7 @@ static ssize_t http_handle_announce(const int64_t sock, struct ot_workstruct
661 677
662 /* Scanned whole query string */ 678 /* Scanned whole query string */
663 if (!ws->hash) 679 if (!ws->hash)
664 return ws->reply_size = sprintf(ws->reply, "d14:failure reason80:Your client forgot to send your torrent's info_hash. Please upgrade your client.e"); 680 return ws->reply_size = reply_literal(ws, "d14:failure reason80:Your client forgot to send your torrent's info_hash. Please upgrade your client.e");
665 681
666 if (OT_PEERFLAG(&ws->peer) & PEER_FLAG_STOPPED) 682 if (OT_PEERFLAG(&ws->peer) & PEER_FLAG_STOPPED)
667 ws->reply_size = remove_peer_from_torrent(FLAG_TCP, ws); 683 ws->reply_size = remove_peer_from_torrent(FLAG_TCP, ws);
@@ -772,9 +788,11 @@ ssize_t http_handle_request(const int64_t sock, struct ot_workstruct *ws) {
772 reply_off = SUCCESS_HTTP_SIZE_OFF - snprintf(ws->outbuf, 0, "%zd", ws->reply_size); 788 reply_off = SUCCESS_HTTP_SIZE_OFF - snprintf(ws->outbuf, 0, "%zd", ws->reply_size);
773 ws->reply = ws->outbuf + reply_off; 789 ws->reply = ws->outbuf + reply_off;
774 790
775 /* 2. Now we sprintf our header so that sprintf writes its terminating '\0' exactly one byte before content starts. Complete 791 /* 2. Now we write the header so that its terminating '\0' lands exactly one byte before the content starts. The bound is the
776 packet size is increased by size of header plus one byte '\n', we will copy over '\0' in next step */ 792 space step 1 reserved, so the '\0' falls where step 3 expects it. Complete packet size is increased by size of header plus
777 ws->reply_size += 1 + sprintf(ws->reply, "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: %zd\r\n\r", ws->reply_size); 793 one byte '\n', we will copy over '\0' in next step */
794 ws->reply_size += 1 + snprintf(ws->reply, (size_t)(SUCCESS_HTTP_HEADER_LENGTH - reply_off),
795 "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: %zd\r\n\r", ws->reply_size);
778 796
779 /* 3. Finally we join both blocks neatly */ 797 /* 3. Finally we join both blocks neatly */
780 ws->outbuf[SUCCESS_HTTP_HEADER_LENGTH - 1] = '\n'; 798 ws->outbuf[SUCCESS_HTTP_HEADER_LENGTH - 1] = '\n';
diff --git a/ot_stats.c b/ot_stats.c
index 53bd749..45dc8db 100644
--- a/ot_stats.c
+++ b/ot_stats.c
@@ -22,6 +22,7 @@
22#include "ot_iovec.h" 22#include "ot_iovec.h"
23#include "ot_ip.h" 23#include "ot_ip.h"
24#include "ot_mutex.h" 24#include "ot_mutex.h"
25#include "ot_fmt.h"
25#include "ot_stats.h" 26#include "ot_stats.h"
26#include "trackerlogic.h" 27#include "trackerlogic.h"
27 28
@@ -184,12 +185,11 @@ static size_t stats_get_highscore_networks(stats_network_node *node, int depth,
184 return score; 185 return score;
185} 186}
186 187
187static size_t stats_return_busy_networks(char *reply, stats_network_node *tree, size_t amount, int limit) { 188static void stats_return_busy_networks(char **reply, const char *reply_end, stats_network_node *tree, size_t amount, int limit) {
188 ot_ip6 networks[amount]; 189 ot_ip6 networks[amount];
189 ot_ip6 node_value; 190 ot_ip6 node_value;
190 size_t scores[amount]; 191 size_t scores[amount];
191 size_t i; 192 size_t i;
192 char *r = reply;
193 193
194 memset(scores, 0, sizeof(scores)); 194 memset(scores, 0, sizeof(scores));
195 memset(networks, 0, sizeof(networks)); 195 memset(networks, 0, sizeof(networks));
@@ -197,24 +197,21 @@ static size_t stats_return_busy_networks(char *reply, stats_network_node *tree,
197 197
198 stats_get_highscore_networks(tree, 0, node_value, scores, networks, amount, limit); 198 stats_get_highscore_networks(tree, 0, node_value, scores, networks, amount, limit);
199 199
200 r += sprintf(r, "Networks, limit /%d:\n", limit + STATS_NETWORK_NODE_BITWIDTH); 200 fmt_append(reply, reply_end, "Networks, limit /%d:\n", limit + STATS_NETWORK_NODE_BITWIDTH);
201 for (i = amount; i > 0; --i) { 201 for (i = amount; i > 0; --i) {
202 if (scores[i - 1]) { 202 if (scores[i - 1]) {
203 r += sprintf(r, "%08zd: ", scores[i - 1]); 203 char ip_readable[64];
204 // #ifdef WANT_V6 204
205 r += ip_format(r, networks[i - 1]); 205 ip_readable[ip_format(ip_readable, networks[i - 1])] = 0;
206 *r++ = '\n'; 206 fmt_append(reply, reply_end, "%08zd: %s\n", scores[i - 1], ip_readable);
207 } 207 }
208 } 208 }
209 *r++ = '\n'; 209 fmt_append(reply, reply_end, "\n");
210
211 return r - reply;
212} 210}
213 211
214static size_t stats_slash24s_txt(char *reply, size_t amount) { 212static void stats_slash24s_txt(char **reply, const char *reply_end, size_t amount) {
215 stats_network_node *slash24s_network_counters_root = NULL; 213 stats_network_node *slash24s_network_counters_root = NULL;
216 char *r = reply; 214 size_t i, bucket, peer_size = OT_PEER_SIZE4;
217 size_t i, bucket, peer_size = OT_PEER_SIZE4;
218 215
219 for (bucket = 0; bucket < OT_BUCKET_COUNT; ++bucket) { 216 for (bucket = 0; bucket < OT_BUCKET_COUNT; ++bucket) {
220 ot_vector *torrents_list = mutex_bucket_lock(bucket); 217 ot_vector *torrents_list = mutex_bucket_lock(bucket);
@@ -241,35 +238,28 @@ static size_t stats_slash24s_txt(char *reply, size_t amount) {
241 } 238 }
242 mutex_bucket_unlock(bucket, 0); 239 mutex_bucket_unlock(bucket, 0);
243 if (!g_opentracker_running) 240 if (!g_opentracker_running)
244 goto bailout_error; 241 goto done;
245 } 242 }
246 243
247 /* The tree is built. Now analyze */ 244 /* The tree is built. Now analyze */
248 r += stats_return_busy_networks(r, slash24s_network_counters_root, amount, STATS_NETWORK_NODE_MAXDEPTH); 245 stats_return_busy_networks(reply, reply_end, slash24s_network_counters_root, amount, STATS_NETWORK_NODE_MAXDEPTH);
249 r += stats_return_busy_networks(r, slash24s_network_counters_root, amount, STATS_NETWORK_NODE_LIMIT); 246 stats_return_busy_networks(reply, reply_end, slash24s_network_counters_root, amount, STATS_NETWORK_NODE_LIMIT);
250 goto success; 247 goto done;
251 248
252bailout_unlock: 249bailout_unlock:
253 mutex_bucket_unlock(bucket, 0); 250 mutex_bucket_unlock(bucket, 0);
254bailout_error: 251done:
255 r = reply;
256success:
257 stats_shift_down_network_count(&slash24s_network_counters_root, 0, sizeof(int) * 8 - 1); 252 stats_shift_down_network_count(&slash24s_network_counters_root, 0, sizeof(int) * 8 - 1);
258
259 return r - reply;
260} 253}
261 254
262#ifdef WANT_SPOT_WOODPECKER 255#ifdef WANT_SPOT_WOODPECKER
263static stats_network_node *stats_woodpeckers_tree; 256static stats_network_node *stats_woodpeckers_tree;
264static pthread_mutex_t g_woodpeckers_mutex = PTHREAD_MUTEX_INITIALIZER; 257static pthread_mutex_t g_woodpeckers_mutex = PTHREAD_MUTEX_INITIALIZER;
265 258
266static size_t stats_return_woodpeckers(char *reply, int amount) { 259static void stats_return_woodpeckers(char **reply, const char *reply_end, int amount) {
267 char *r = reply;
268
269 pthread_mutex_lock(&g_woodpeckers_mutex); 260 pthread_mutex_lock(&g_woodpeckers_mutex);
270 r += stats_return_busy_networks(r, stats_woodpeckers_tree, amount, STATS_NETWORK_NODE_MAXDEPTH); 261 stats_return_busy_networks(reply, reply_end, stats_woodpeckers_tree, amount, STATS_NETWORK_NODE_MAXDEPTH);
271 pthread_mutex_unlock(&g_woodpeckers_mutex); 262 pthread_mutex_unlock(&g_woodpeckers_mutex);
272 return r - reply;
273} 263}
274#endif 264#endif
275 265
@@ -309,10 +299,10 @@ static inline void stats_insert_into_highscore(size_t val, ot_record* list, size
309 299
310 300
311/* Fetches stats from tracker */ 301/* Fetches stats from tracker */
312size_t stats_top_txt(char *reply, size_t amount) { 302void stats_top_txt(char **reply, const char *reply_end, size_t amount) {
313 size_t j, idx, bucket; 303 size_t j, idx, bucket;
314 ot_record top100s[100], top100c[100], top100l[100]; 304 ot_record top100s[100], top100c[100], top100l[100];
315 char *r = reply, hex_out[42]; 305 char hex_out[42];
316 306
317 if (amount > 100) 307 if (amount > 100)
318 amount = 100; 308 amount = 100;
@@ -323,7 +313,7 @@ size_t stats_top_txt(char *reply, size_t amount) {
323 313
324 /* Iterate over complete torrent list */ 314 /* Iterate over complete torrent list */
325 for (bucket = 0; bucket < OT_BUCKET_COUNT; ++bucket) { 315 for (bucket = 0; bucket < OT_BUCKET_COUNT; ++bucket) {
326 ot_vector *torrents_list = mutex_bucket_lock(bucket); 316 ot_vector *torrents_list = mutex_bucket_lock(bucket);
327 for (j = 0; j < torrents_list->size; ++j) { 317 for (j = 0; j < torrents_list->size; ++j) {
328 ot_torrent *torrent = (ot_torrent *)(torrents_list->data) + j; 318 ot_torrent *torrent = (ot_torrent *)(torrents_list->data) + j;
329 size_t peer_count = torrent->peer_list6->peer_count + torrent->peer_list4->peer_count; 319 size_t peer_count = torrent->peer_list6->peer_count + torrent->peer_list4->peer_count;
@@ -336,114 +326,110 @@ size_t stats_top_txt(char *reply, size_t amount) {
336 } 326 }
337 mutex_bucket_unlock(bucket, 0); 327 mutex_bucket_unlock(bucket, 0);
338 if (!g_opentracker_running) 328 if (!g_opentracker_running)
339 return 0; 329 return;
340 } 330 }
341 331
342 r += sprintf(r, "Top %zd torrents by peers:\n", amount); 332 fmt_append(reply, reply_end, "Top %zd torrents by peers:\n", amount);
343 for (idx = 0; idx < amount; ++idx) 333 for (idx = 0; idx < amount; ++idx)
344 if (top100c[idx].val) 334 if (top100c[idx].val)
345 r += sprintf(r, "\t%zd\t%s\n", top100c[idx].val, hex_from_hash(hex_out, top100c[idx].hash)); 335 fmt_append(reply, reply_end, "\t%zd\t%s\n", top100c[idx].val, hex_from_hash(hex_out, top100c[idx].hash));
346 r += sprintf(r, "Top %zd torrents by seeds:\n", amount); 336 fmt_append(reply, reply_end, "Top %zd torrents by seeds:\n", amount);
347 for (idx = 0; idx < amount; ++idx) 337 for (idx = 0; idx < amount; ++idx)
348 if (top100s[idx].val) 338 if (top100s[idx].val)
349 r += sprintf(r, "\t%zd\t%s\n", top100s[idx].val, hex_from_hash(hex_out, top100s[idx].hash)); 339 fmt_append(reply, reply_end, "\t%zd\t%s\n", top100s[idx].val, hex_from_hash(hex_out, top100s[idx].hash));
350 r += sprintf(r, "Top %zd torrents by leechers:\n", amount); 340 fmt_append(reply, reply_end, "Top %zd torrents by leechers:\n", amount);
351 for (idx = 0; idx < amount; ++idx) 341 for (idx = 0; idx < amount; ++idx)
352 if (top100l[idx].val) 342 if (top100l[idx].val)
353 r += sprintf(r, "\t%zd\t%s\n", top100l[idx].val, hex_from_hash(hex_out, top100l[idx].hash)); 343 fmt_append(reply, reply_end, "\t%zd\t%s\n", top100l[idx].val, hex_from_hash(hex_out, top100l[idx].hash));
354
355 return r - reply;
356} 344}
357 345
358static unsigned long events_per_time(unsigned long long events, time_t t) { return events / ((unsigned int)t ? (unsigned int)t : 1); } 346static unsigned long events_per_time(unsigned long long events, time_t t) { return events / ((unsigned int)t ? (unsigned int)t : 1); }
359 347
360static size_t stats_connections_mrtg(char *reply) { 348static void stats_connections_mrtg(char **reply, const char *reply_end) {
361 ot_time t = time(NULL) - ot_start_time; 349 ot_time t = time(NULL) - ot_start_time;
362 return sprintf(reply, "%llu\n%llu\n%i seconds (%i hours)\nopentracker connections, %lu conns/s :: %lu success/s.", 350 fmt_append(reply, reply_end, "%llu\n%llu\n%i seconds (%i hours)\nopentracker connections, %lu conns/s :: %lu success/s.",
363 ot_overall_tcp_connections + ot_overall_udp_connections, 351 ot_overall_tcp_connections + ot_overall_udp_connections,
364 ot_overall_tcp_successfulannounces + ot_overall_udp_successfulannounces + ot_overall_udp_connects, (int)t, (int)(t / 3600), 352 ot_overall_tcp_successfulannounces + ot_overall_udp_successfulannounces + ot_overall_udp_connects, (int)t, (int)(t / 3600),
365 events_per_time(ot_overall_tcp_connections + ot_overall_udp_connections, t), 353 events_per_time(ot_overall_tcp_connections + ot_overall_udp_connections, t),
366 events_per_time(ot_overall_tcp_successfulannounces + ot_overall_udp_successfulannounces + ot_overall_udp_connects, t)); 354 events_per_time(ot_overall_tcp_successfulannounces + ot_overall_udp_successfulannounces + ot_overall_udp_connects, t));
367} 355}
368 356
369static size_t stats_udpconnections_mrtg(char *reply) { 357static void stats_udpconnections_mrtg(char **reply, const char *reply_end) {
370 ot_time t = time(NULL) - ot_start_time; 358 ot_time t = time(NULL) - ot_start_time;
371 return sprintf(reply, "%llu\n%llu\n%i seconds (%i hours)\nopentracker udp4 stats, %lu conns/s :: %lu success/s.", ot_overall_udp_connections, 359 fmt_append(reply, reply_end, "%llu\n%llu\n%i seconds (%i hours)\nopentracker udp4 stats, %lu conns/s :: %lu success/s.", ot_overall_udp_connections,
372 ot_overall_udp_successfulannounces + ot_overall_udp_connects, (int)t, (int)(t / 3600), events_per_time(ot_overall_udp_connections, t), 360 ot_overall_udp_successfulannounces + ot_overall_udp_connects, (int)t, (int)(t / 3600), events_per_time(ot_overall_udp_connections, t),
373 events_per_time(ot_overall_udp_successfulannounces + ot_overall_udp_connects, t)); 361 events_per_time(ot_overall_udp_successfulannounces + ot_overall_udp_connects, t));
374} 362}
375 363
376static size_t stats_tcpconnections_mrtg(char *reply) { 364static void stats_tcpconnections_mrtg(char **reply, const char *reply_end) {
377 time_t t = time(NULL) - ot_start_time; 365 time_t t = time(NULL) - ot_start_time;
378 return sprintf(reply, "%llu\n%llu\n%i seconds (%i hours)\nopentracker tcp4 stats, %lu conns/s :: %lu success/s.", ot_overall_tcp_connections, 366 fmt_append(reply, reply_end, "%llu\n%llu\n%i seconds (%i hours)\nopentracker tcp4 stats, %lu conns/s :: %lu success/s.", ot_overall_tcp_connections,
379 ot_overall_tcp_successfulannounces, (int)t, (int)(t / 3600), events_per_time(ot_overall_tcp_connections, t), 367 ot_overall_tcp_successfulannounces, (int)t, (int)(t / 3600), events_per_time(ot_overall_tcp_connections, t),
380 events_per_time(ot_overall_tcp_successfulannounces, t)); 368 events_per_time(ot_overall_tcp_successfulannounces, t));
381} 369}
382 370
383static size_t stats_scrape_mrtg(char *reply) { 371static void stats_scrape_mrtg(char **reply, const char *reply_end) {
384 time_t t = time(NULL) - ot_start_time; 372 time_t t = time(NULL) - ot_start_time;
385 return sprintf(reply, "%llu\n%llu\n%i seconds (%i hours)\nopentracker scrape stats, %lu scrape/s (tcp and udp)", ot_overall_tcp_successfulscrapes, 373 fmt_append(reply, reply_end, "%llu\n%llu\n%i seconds (%i hours)\nopentracker scrape stats, %lu scrape/s (tcp and udp)",
386 ot_overall_udp_successfulscrapes, (int)t, (int)(t / 3600), 374 ot_overall_tcp_successfulscrapes, ot_overall_udp_successfulscrapes, (int)t, (int)(t / 3600),
387 events_per_time((ot_overall_tcp_successfulscrapes + ot_overall_udp_successfulscrapes), t)); 375 events_per_time((ot_overall_tcp_successfulscrapes + ot_overall_udp_successfulscrapes), t));
388} 376}
389 377
390static size_t stats_fullscrapes_mrtg(char *reply) { 378static void stats_fullscrapes_mrtg(char **reply, const char *reply_end) {
391 ot_time t = time(NULL) - ot_start_time; 379 ot_time t = time(NULL) - ot_start_time;
392 return sprintf(reply, "%llu\n%llu\n%i seconds (%i hours)\nopentracker full scrape stats, %lu conns/s :: %lu bytes/s.", ot_full_scrape_count * 1000, 380 fmt_append(reply, reply_end, "%llu\n%llu\n%i seconds (%i hours)\nopentracker full scrape stats, %lu conns/s :: %lu bytes/s.",
393 ot_full_scrape_size, (int)t, (int)(t / 3600), events_per_time(ot_full_scrape_count, t), events_per_time(ot_full_scrape_size, t)); 381 ot_full_scrape_count * 1000, ot_full_scrape_size, (int)t, (int)(t / 3600), events_per_time(ot_full_scrape_count, t),
382 events_per_time(ot_full_scrape_size, t));
394} 383}
395 384
396static size_t stats_peers_mrtg(char *reply) { 385static void stats_peers_mrtg(char **reply, const char *reply_end) {
397 torrent_stats stats = {0, 0, 0}; 386 torrent_stats stats = {0, 0, 0};
398 387
399 iterate_all_torrents(torrent_statter, (uintptr_t)&stats); 388 iterate_all_torrents(torrent_statter, (uintptr_t)&stats);
400 389
401 return sprintf(reply, "%llu\n%llu\nopentracker serving %llu torrents\nopentracker", stats.peer_count, stats.seed_count, stats.torrent_count); 390 fmt_append(reply, reply_end, "%llu\n%llu\nopentracker serving %llu torrents\nopentracker", stats.peer_count, stats.seed_count,
391 stats.torrent_count);
402} 392}
403 393
404static size_t stats_torrents_mrtg(char *reply) { 394static void stats_torrents_mrtg(char **reply, const char *reply_end) {
405 size_t torrent_count = mutex_get_torrent_count(); 395 size_t torrent_count = mutex_get_torrent_count();
406 396
407 return sprintf(reply, "%zd\n%zd\nopentracker serving %zd torrents\nopentracker", torrent_count, (size_t)0, torrent_count); 397 fmt_append(reply, reply_end, "%zd\n%zd\nopentracker serving %zd torrents\nopentracker", torrent_count, (size_t)0, torrent_count);
408} 398}
409 399
410static size_t stats_httperrors_txt(char *reply) { 400static void stats_httperrors_txt(char **reply, const char *reply_end) {
411 return sprintf(reply, "302 RED %llu\n400 ... %llu\n400 PAR %llu\n400 COM %llu\n403 IP %llu\n404 INV %llu\n500 SRV %llu\n", ot_failed_request_counts[0], 401 fmt_append(reply, reply_end, "302 RED %llu\n400 ... %llu\n400 PAR %llu\n400 COM %llu\n403 IP %llu\n404 INV %llu\n500 SRV %llu\n",
412 ot_failed_request_counts[1], ot_failed_request_counts[2], ot_failed_request_counts[3], ot_failed_request_counts[4], 402 ot_failed_request_counts[0], ot_failed_request_counts[1], ot_failed_request_counts[2], ot_failed_request_counts[3],
413 ot_failed_request_counts[5], ot_failed_request_counts[6]); 403 ot_failed_request_counts[4], ot_failed_request_counts[5], ot_failed_request_counts[6]);
414} 404}
415 405
416static size_t stats_return_renew_bucket(char *reply) { 406static void stats_return_renew_bucket(char **reply, const char *reply_end) {
417 char *r = reply; 407 int i;
418 int i;
419 408
420 for (i = 0; i < OT_PEER_TIMEOUT; ++i) 409 for (i = 0; i < OT_PEER_TIMEOUT; ++i)
421 r += sprintf(r, "%02i %llu\n", i, ot_renewed[i]); 410 fmt_append(reply, reply_end, "%02i %llu\n", i, ot_renewed[i]);
422 return r - reply;
423} 411}
424 412
425static size_t stats_return_sync_mrtg(char *reply) { 413static void stats_return_sync_mrtg(char **reply, const char *reply_end) {
426 ot_time t = time(NULL) - ot_start_time; 414 ot_time t = time(NULL) - ot_start_time;
427 return sprintf(reply, "%llu\n%llu\n%i seconds (%i hours)\nopentracker connections, %lu conns/s :: %lu success/s.", ot_overall_sync_count, 0LL, (int)t, 415 fmt_append(reply, reply_end, "%llu\n%llu\n%i seconds (%i hours)\nopentracker connections, %lu conns/s :: %lu success/s.", ot_overall_sync_count, 0LL,
428 (int)(t / 3600), events_per_time(ot_overall_tcp_connections + ot_overall_udp_connections, t), 416 (int)t, (int)(t / 3600), events_per_time(ot_overall_tcp_connections + ot_overall_udp_connections, t),
429 events_per_time(ot_overall_tcp_successfulannounces + ot_overall_udp_successfulannounces + ot_overall_udp_connects, t)); 417 events_per_time(ot_overall_tcp_successfulannounces + ot_overall_udp_successfulannounces + ot_overall_udp_connects, t));
430} 418}
431 419
432static size_t stats_return_completed_mrtg(char *reply) { 420static void stats_return_completed_mrtg(char **reply, const char *reply_end) {
433 ot_time t = time(NULL) - ot_start_time; 421 ot_time t = time(NULL) - ot_start_time;
434 422
435 return sprintf(reply, "%llu\n%llu\n%i seconds (%i hours)\nopentracker, %lu completed/h.", ot_overall_completed, 0LL, (int)t, (int)(t / 3600), 423 fmt_append(reply, reply_end, "%llu\n%llu\n%i seconds (%i hours)\nopentracker, %lu completed/h.", ot_overall_completed, 0LL, (int)t, (int)(t / 3600),
436 events_per_time(ot_overall_completed, t / 3600)); 424 events_per_time(ot_overall_completed, t / 3600));
437} 425}
438 426
439#ifdef WANT_LOG_NUMWANT 427#ifdef WANT_LOG_NUMWANT
440extern unsigned long long numwants[201]; 428extern unsigned long long numwants[201];
441static size_t stats_return_numwants(char *reply) { 429static void stats_return_numwants(char **reply, const char *reply_end) {
442 char *r = reply; 430 int i;
443 int i;
444 for (i = 0; i <= 200; ++i) 431 for (i = 0; i <= 200; ++i)
445 r += sprintf(r, "%03d => %lld\n", i, numwants[i]); 432 fmt_append(reply, reply_end, "%03d => %lld\n", i, numwants[i]);
446 return r - reply;
447} 433}
448#endif 434#endif
449 435
@@ -461,17 +447,14 @@ static void stats_return_fulllog(size_t *iovec_entries, struct iovec **iovector,
461 return; 447 return;
462 re = r + 32 * OT_STATS_TMPSIZE; 448 re = r + 32 * OT_STATS_TMPSIZE;
463 } 449 }
464 r += sprintf(r, "%08ld: ", loglist->time); 450 char ip_readable[64];
465 r += ip_format(r, loglist->ip); 451
466 *r++ = '\n'; 452 ip_readable[ip_format(ip_readable, loglist->ip)] = 0;
467 memcpy(r, loglist->data, loglist->size); 453 fmt_append(&r, re, "%08ld: %s\n", loglist->time, ip_readable);
468 r += loglist->size; 454 fmt_bytes(&r, re, loglist->data, loglist->size);
469 *r++ = '\n'; 455 fmt_append(&r, re, "\n*\n\n");
470 *r++ = '*';
471 *r++ = '\n';
472 *r++ = '\n';
473 456
474 llnext = loglist->next; 457 llnext = loglist->next;
475 free(loglist->data); 458 free(loglist->data);
476 free(loglist); 459 free(loglist);
477 loglist = llnext; 460 loglist = llnext;
@@ -480,124 +463,137 @@ static void stats_return_fulllog(size_t *iovec_entries, struct iovec **iovector,
480} 463}
481#endif 464#endif
482 465
483static size_t stats_return_everything(char *reply) { 466static void stats_return_everything(char **reply, const char *reply_end) {
484 torrent_stats stats = {0, 0, 0}; 467 torrent_stats stats = {0, 0, 0};
485 int i; 468 int i;
486 char *r = reply;
487 469
488 iterate_all_torrents(torrent_statter, (uintptr_t)&stats); 470 iterate_all_torrents(torrent_statter, (uintptr_t)&stats);
489 471
490 r += sprintf(r, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"); 472 fmt_append(reply, reply_end, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
491 r += sprintf(r, "<stats>\n"); 473 fmt_append(reply, reply_end, "<stats>\n");
492 r += sprintf(r, " <tracker_id>%" PRIu32 "</tracker_id>\n", g_tracker_id); 474 fmt_append(reply, reply_end, " <tracker_id>%" PRIu32 "</tracker_id>\n", g_tracker_id);
493 r += sprintf(r, " <version>\n"); 475 fmt_append(reply, reply_end, " <version>\n");
494 r += stats_return_tracker_version(r); 476 stats_return_tracker_version(reply, reply_end);
495 r += sprintf(r, " </version>\n"); 477 fmt_append(reply, reply_end, " </version>\n");
496 r += sprintf(r, " <uptime>%llu</uptime>\n", (unsigned long long)(time(NULL) - ot_start_time)); 478 fmt_append(reply, reply_end, " <uptime>%llu</uptime>\n", (unsigned long long)(time(NULL) - ot_start_time));
497 r += sprintf(r, " <torrents>\n"); 479 fmt_append(reply, reply_end, " <torrents>\n");
498 r += sprintf(r, " <count_mutex>%zd</count_mutex>\n", mutex_get_torrent_count()); 480 fmt_append(reply, reply_end, " <count_mutex>%zd</count_mutex>\n", mutex_get_torrent_count());
499 r += sprintf(r, " <count_iterator>%llu</count_iterator>\n", stats.torrent_count); 481 fmt_append(reply, reply_end, " <count_iterator>%llu</count_iterator>\n", stats.torrent_count);
500 r += sprintf(r, " </torrents>\n"); 482 fmt_append(reply, reply_end, " </torrents>\n");
501 r += sprintf(r, " <peers>\n <count>%llu</count>\n </peers>\n", stats.peer_count); 483 fmt_append(reply, reply_end, " <peers>\n <count>%llu</count>\n </peers>\n", stats.peer_count);
502 r += sprintf(r, " <seeds>\n <count>%llu</count>\n </seeds>\n", stats.seed_count); 484 fmt_append(reply, reply_end, " <seeds>\n <count>%llu</count>\n </seeds>\n", stats.seed_count);
503 r += sprintf(r, " <completed>\n <count>%llu</count>\n </completed>\n", ot_overall_completed); 485 fmt_append(reply, reply_end, " <completed>\n <count>%llu</count>\n </completed>\n", ot_overall_completed);
504 r += sprintf(r, " <connections>\n"); 486 fmt_append(reply, reply_end, " <connections>\n");
505 r += sprintf(r, " <tcp>\n <accept>%llu</accept>\n <announce>%llu</announce>\n <scrape>%llu</scrape>\n </tcp>\n", 487 fmt_append(reply, reply_end, " <tcp>\n <accept>%llu</accept>\n <announce>%llu</announce>\n <scrape>%llu</scrape>\n </tcp>\n",
506 ot_overall_tcp_connections, ot_overall_tcp_successfulannounces, ot_overall_tcp_successfulscrapes); 488 ot_overall_tcp_connections, ot_overall_tcp_successfulannounces, ot_overall_tcp_successfulscrapes);
507 r += sprintf(r, " <udp>\n <overall>%llu</overall>\n <connect>%llu</connect>\n <announce>%llu</announce>\n <scrape>%llu</scrape>\n <missmatch>%llu</missmatch>\n </udp>\n", 489 fmt_append(reply, reply_end, " <udp>\n <overall>%llu</overall>\n <connect>%llu</connect>\n <announce>%llu</announce>\n <scrape>%llu</scrape>\n <missmatch>%llu</missmatch>\n </udp>\n",
508 ot_overall_udp_connections, ot_overall_udp_connects, ot_overall_udp_successfulannounces, ot_overall_udp_successfulscrapes, 490 ot_overall_udp_connections, ot_overall_udp_connects, ot_overall_udp_successfulannounces, ot_overall_udp_successfulscrapes,
509 ot_overall_udp_connectionidmissmatches); 491 ot_overall_udp_connectionidmissmatches);
510 r += sprintf(r, " <livesync>\n <count>%llu</count>\n </livesync>\n", ot_overall_sync_count); 492 fmt_append(reply, reply_end, " <livesync>\n <count>%llu</count>\n </livesync>\n", ot_overall_sync_count);
511 r += sprintf(r, " </connections>\n"); 493 fmt_append(reply, reply_end, " </connections>\n");
512 r += sprintf(r, " <debug>\n"); 494 fmt_append(reply, reply_end, " <debug>\n");
513 r += sprintf(r, " <renew>\n"); 495 fmt_append(reply, reply_end, " <renew>\n");
514 for (i = 0; i < OT_PEER_TIMEOUT; ++i) 496 for (i = 0; i < OT_PEER_TIMEOUT; ++i)
515 r += sprintf(r, " <count interval=\"%02i\">%llu</count>\n", i, ot_renewed[i]); 497 fmt_append(reply, reply_end, " <count interval=\"%02i\">%llu</count>\n", i, ot_renewed[i]);
516 r += sprintf(r, " </renew>\n"); 498 fmt_append(reply, reply_end, " </renew>\n");
517 r += sprintf(r, " <http_error>\n"); 499 fmt_append(reply, reply_end, " <http_error>\n");
518 for (i = 0; i < CODE_HTTPERROR_COUNT; ++i) 500 for (i = 0; i < CODE_HTTPERROR_COUNT; ++i)
519 r += sprintf(r, " <count code=\"%s\">%llu</count>\n", ot_failed_request_names[i], ot_failed_request_counts[i]); 501 fmt_append(reply, reply_end, " <count code=\"%s\">%llu</count>\n", ot_failed_request_names[i], ot_failed_request_counts[i]);
520 r += sprintf(r, " </http_error>\n"); 502 fmt_append(reply, reply_end, " </http_error>\n");
521 r += sprintf(r, " <mutex_stall>\n <count>%llu</count>\n </mutex_stall>\n", ot_overall_stall_count); 503 fmt_append(reply, reply_end, " <mutex_stall>\n <count>%llu</count>\n </mutex_stall>\n", ot_overall_stall_count);
522 r += sprintf(r, " </debug>\n"); 504 fmt_append(reply, reply_end, " </debug>\n");
523 r += sprintf(r, "</stats>"); 505 fmt_append(reply, reply_end, "</stats>");
524 return r - reply;
525} 506}
526 507
527size_t stats_return_tracker_version(char *reply) { 508void stats_return_tracker_version(char **reply, const char *reply_end) {
528#define QUOTE(name) #name 509#define QUOTE(name) #name
529#define SQUOTE(name) QUOTE(name) 510#define SQUOTE(name) QUOTE(name)
530 return sprintf(reply, "https://erdgeist.org/gitweb/opentracker/commit/?id=" SQUOTE(GIT_VERSION) "\n"); 511 fmt_append(reply, reply_end, "https://erdgeist.org/gitweb/opentracker/commit/?id=" SQUOTE(GIT_VERSION) "\n");
531} 512}
532 513
533size_t return_stats_for_tracker(char *reply, int mode, int format) { 514void return_stats_for_tracker(char **reply, const char *reply_end, int mode, int format) {
534 (void)format; 515 (void)format;
535 switch (mode & TASK_TASK_MASK) { 516 switch (mode & TASK_TASK_MASK) {
536 case TASK_STATS_CONNS: 517 case TASK_STATS_CONNS:
537 return stats_connections_mrtg(reply); 518 stats_connections_mrtg(reply, reply_end);
519 break;
538 case TASK_STATS_SCRAPE: 520 case TASK_STATS_SCRAPE:
539 return stats_scrape_mrtg(reply); 521 stats_scrape_mrtg(reply, reply_end);
522 break;
540 case TASK_STATS_UDP: 523 case TASK_STATS_UDP:
541 return stats_udpconnections_mrtg(reply); 524 stats_udpconnections_mrtg(reply, reply_end);
525 break;
542 case TASK_STATS_TCP: 526 case TASK_STATS_TCP:
543 return stats_tcpconnections_mrtg(reply); 527 stats_tcpconnections_mrtg(reply, reply_end);
528 break;
544 case TASK_STATS_FULLSCRAPE: 529 case TASK_STATS_FULLSCRAPE:
545 return stats_fullscrapes_mrtg(reply); 530 stats_fullscrapes_mrtg(reply, reply_end);
531 break;
546 case TASK_STATS_COMPLETED: 532 case TASK_STATS_COMPLETED:
547 return stats_return_completed_mrtg(reply); 533 stats_return_completed_mrtg(reply, reply_end);
534 break;
548 case TASK_STATS_HTTPERRORS: 535 case TASK_STATS_HTTPERRORS:
549 return stats_httperrors_txt(reply); 536 stats_httperrors_txt(reply, reply_end);
537 break;
550 case TASK_STATS_VERSION: 538 case TASK_STATS_VERSION:
551 return stats_return_tracker_version(reply); 539 stats_return_tracker_version(reply, reply_end);
540 break;
552 case TASK_STATS_RENEW: 541 case TASK_STATS_RENEW:
553 return stats_return_renew_bucket(reply); 542 stats_return_renew_bucket(reply, reply_end);
543 break;
554 case TASK_STATS_SYNCS: 544 case TASK_STATS_SYNCS:
555 return stats_return_sync_mrtg(reply); 545 stats_return_sync_mrtg(reply, reply_end);
546 break;
556#ifdef WANT_LOG_NUMWANT 547#ifdef WANT_LOG_NUMWANT
557 case TASK_STATS_NUMWANTS: 548 case TASK_STATS_NUMWANTS:
558 return stats_return_numwants(reply); 549 stats_return_numwants(reply, reply_end);
550 break;
559#endif 551#endif
560 default: 552 default:
561 return 0; 553 break;
562 } 554 }
563} 555}
564 556
565static void stats_make(size_t *iovec_entries, struct iovec **iovector, ot_tasktype mode) { 557static void stats_make(size_t *iovec_entries, struct iovec **iovector, ot_tasktype mode) {
566 char *r; 558 char *r;
559 const char *r_end;
567 560
568 *iovec_entries = 0; 561 *iovec_entries = 0;
569 *iovector = NULL; 562 *iovector = NULL;
570 if (!(r = iovec_new_chunk(iovec_entries, iovector, OT_STATS_TMPSIZE))) 563 if (!(r = iovec_new_chunk(iovec_entries, iovector, OT_STATS_TMPSIZE)))
571 return; 564 return;
565 r_end = r + OT_STATS_TMPSIZE;
572 566
573 switch (mode & TASK_TASK_MASK) { 567 switch (mode & TASK_TASK_MASK) {
574 case TASK_STATS_TORRENTS: 568 case TASK_STATS_TORRENTS:
575 r += stats_torrents_mrtg(r); 569 stats_torrents_mrtg(&r, r_end);
576 break; 570 break;
577 case TASK_STATS_PEERS: 571 case TASK_STATS_PEERS:
578 r += stats_peers_mrtg(r); 572 stats_peers_mrtg(&r, r_end);
579 break; 573 break;
580 case TASK_STATS_SLASH24S: 574 case TASK_STATS_SLASH24S:
581 r += stats_slash24s_txt(r, 128); 575 stats_slash24s_txt(&r, r_end, 128);
582 break; 576 break;
583 case TASK_STATS_TOP10: 577 case TASK_STATS_TOP10:
584 r += stats_top_txt(r, 10); 578 stats_top_txt(&r, r_end, 10);
585 break; 579 break;
586 case TASK_STATS_TOP100: 580 case TASK_STATS_TOP100:
587 r = iovec_next_chunk(iovec_entries, iovector, r, 4 * OT_STATS_TMPSIZE); 581 r = iovec_next_chunk(iovec_entries, iovector, r, 4 * OT_STATS_TMPSIZE);
588 if (!r) 582 if (!r)
589 return; 583 return;
590 r += stats_top_txt(r, 100); 584 r_end = r + 4 * OT_STATS_TMPSIZE;
585 stats_top_txt(&r, r_end, 100);
591 break; 586 break;
592 case TASK_STATS_EVERYTHING: 587 case TASK_STATS_EVERYTHING:
593 r = iovec_next_chunk(iovec_entries, iovector, r, OT_STATS_TMPSIZE + 64 * OT_PEER_TIMEOUT); 588 r = iovec_next_chunk(iovec_entries, iovector, r, OT_STATS_TMPSIZE + 64 * OT_PEER_TIMEOUT);
594 if (!r) 589 if (!r)
595 return; 590 return;
596 r += stats_return_everything(r); 591 r_end = r + OT_STATS_TMPSIZE + 64 * OT_PEER_TIMEOUT;
592 stats_return_everything(&r, r_end);
597 break; 593 break;
598#ifdef WANT_SPOT_WOODPECKER 594#ifdef WANT_SPOT_WOODPECKER
599 case TASK_STATS_WOODPECKERS: 595 case TASK_STATS_WOODPECKERS:
600 r += stats_return_woodpeckers(r, 128); 596 stats_return_woodpeckers(&r, r_end, 128);
601 break; 597 break;
602#endif 598#endif
603#ifdef WANT_FULLLOG_NETWORKS 599#ifdef WANT_FULLLOG_NETWORKS
diff --git a/ot_stats.h b/ot_stats.h
index 203831c..fb6aad8 100644
--- a/ot_stats.h
+++ b/ot_stats.h
@@ -44,8 +44,8 @@ enum {
44void stats_issue_event(ot_status_event event, PROTO_FLAG proto, uintptr_t event_data); 44void stats_issue_event(ot_status_event event, PROTO_FLAG proto, uintptr_t event_data);
45void stats_deliver(int64_t sock, int tasktype); 45void stats_deliver(int64_t sock, int tasktype);
46void stats_cleanup(void); 46void stats_cleanup(void);
47size_t return_stats_for_tracker(char *reply, int mode, int format); 47void return_stats_for_tracker(char **reply, const char *reply_end, int mode, int format);
48size_t stats_return_tracker_version(char *reply); 48void stats_return_tracker_version(char **reply, const char *reply_end);
49void stats_init(void); 49void stats_init(void);
50void stats_deinit(void); 50void stats_deinit(void);
51 51
diff --git a/ot_sync.c b/ot_sync.c
index f958a20..f656bef 100644
--- a/ot_sync.c
+++ b/ot_sync.c
@@ -11,6 +11,7 @@
11#include <sys/uio.h> 11#include <sys/uio.h>
12 12
13/* Opentracker */ 13/* Opentracker */
14#include "ot_fmt.h"
14#include "ot_iovec.h" 15#include "ot_iovec.h"
15#include "ot_mutex.h" 16#include "ot_mutex.h"
16#include "ot_scan.h" 17#include "ot_scan.h"
@@ -109,14 +110,10 @@ static void sync_make(size_t *iovec_entries, struct iovec **iovector) {
109 re = r + OT_SYNC_CHUNK_SIZE; 110 re = r + OT_SYNC_CHUNK_SIZE;
110 } 111 }
111 112
112 *r++ = '2'; 113 fmt_append(&r, re, "20:");
113 *r++ = '0'; 114 fmt_bytes(&r, re, hash, sizeof(ot_hash));
114 *r++ = ':'; 115 fmt_append(&r, re, "%zd:", byte_count);
115 memmove(r, hash, sizeof(ot_hash)); 116 fmt_bytes(&r, re, peer_list->changeset.data, byte_count);
116 r += sizeof(ot_hash);
117 r += sprintf(r, "%zd:", byte_count);
118 memmove(r, peer_list->changeset.data, byte_count);
119 r += byte_count;
120 } 117 }
121 118
122 /* All torrents done: release lock on currenct bucket */ 119 /* All torrents done: release lock on currenct bucket */
diff --git a/tests/runner.c b/tests/runner.c
index c2350c3..a92315e 100644
--- a/tests/runner.c
+++ b/tests/runner.c
@@ -27,6 +27,7 @@ static const struct module modules[] = {{"ot_urlencoded", test_urlencoded},
27 {"ot_batch", test_batch}, 27 {"ot_batch", test_batch},
28 {"ot_fdstate", test_fdstate}, 28 {"ot_fdstate", test_fdstate},
29 {"ot_event", test_event}, 29 {"ot_event", test_event},
30 {"ot_fmt", test_fmt},
30 {"ot_accesslist", test_accesslist}, 31 {"ot_accesslist", test_accesslist},
31 {NULL, NULL}}; 32 {NULL, NULL}};
32 33
diff --git a/tests/t_fmt.c b/tests/t_fmt.c
new file mode 100644
index 0000000..bc23ed5
--- /dev/null
+++ b/tests/t_fmt.c
@@ -0,0 +1,108 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever.
3
4 $id$ */
5
6/* System */
7#include <string.h>
8
9/* Opentracker */
10#include "ot_fmt.h"
11#include "test.h"
12
13static void case_appends_in_sequence(void) {
14 char buffer[64];
15 char *r = buffer, *re = buffer + sizeof(buffer);
16
17 test_case("appends follow each other and advance the cursor");
18 fmt_append(&r, re, "d8:completei%zde", (size_t)7);
19 fmt_append(&r, re, "ee");
20 *r = 0;
21 CHECK_STR(buffer, "d8:completei7eee");
22 CHECK_INT(r - buffer, 16);
23}
24
25static void case_truncation_stops_at_the_end(void) {
26 char buffer[16];
27 char *r = buffer, *re = buffer + 8; /* pretend the buffer ends early */
28
29 memset(buffer, '#', sizeof(buffer));
30 test_case("a value too long for the room left stops at the end");
31 fmt_append(&r, re, "%s", "0123456789");
32 CHECK_PTR(r, re);
33
34 test_case("and writes nothing past it");
35 CHECK_INT(buffer[8], '#');
36 CHECK_INT(buffer[15], '#');
37}
38
39static void case_a_full_buffer_takes_nothing_more(void) {
40 char buffer[16];
41 char *r = buffer + 8, *re = buffer + 8;
42
43 memset(buffer, '#', sizeof(buffer));
44 test_case("appending to a cursor already at the end changes nothing");
45 fmt_append(&r, re, "more");
46 CHECK_PTR(r, re);
47 CHECK_INT(buffer[8], '#');
48}
49
50static void case_an_append_that_just_fits(void) {
51 char buffer[8];
52 char *r = buffer, *re = buffer + 3;
53
54 memset(buffer, '#', sizeof(buffer));
55 test_case("three characters into three bytes of room still truncate");
56 fmt_append(&r, re, "abc");
57 CHECK_PTR(r, re);
58 CHECK_INT(buffer[0], 'a');
59 CHECK_INT(buffer[1], 'b');
60
61 test_case("because snprintf spends the last byte on its terminator");
62 CHECK_INT(buffer[2], 0);
63 CHECK_INT(buffer[3], '#');
64}
65
66static void case_bytes_are_all_or_nothing(void) {
67 char buffer[16];
68 char *r = buffer, *re = buffer + 8;
69
70 memset(buffer, '#', sizeof(buffer));
71 test_case("a run of bytes is written when it fits");
72 fmt_bytes(&r, re, "\0\1\2\3", 4);
73 CHECK_PTR(r, buffer + 4);
74 CHECK_MEM(buffer, "\0\1\2\3", 4);
75
76 test_case("a run that would not fit is refused whole");
77 fmt_bytes(&r, re, "abcde", 5);
78 CHECK_PTR(r, re);
79 CHECK_INT(buffer[4], '#');
80
81 test_case("a run of exactly the room left is written");
82 r = buffer + 4;
83 fmt_bytes(&r, re, "wxyz", 4);
84 CHECK_PTR(r, re);
85 CHECK_MEM(buffer + 4, "wxyz", 4);
86 CHECK_INT(buffer[8], '#');
87}
88
89static void case_bytes_do_not_terminate(void) {
90 char buffer[8];
91 char *r = buffer, *re = buffer + 4;
92
93 memset(buffer, '#', sizeof(buffer));
94 test_case("unlike an append, a run of bytes may fill the room exactly");
95 fmt_bytes(&r, re, "abcd", 4);
96 CHECK_PTR(r, re);
97 CHECK_MEM(buffer, "abcd", 4);
98 CHECK_INT(buffer[4], '#');
99}
100
101void test_fmt(void) {
102 case_appends_in_sequence();
103 case_truncation_stops_at_the_end();
104 case_a_full_buffer_takes_nothing_more();
105 case_an_append_that_just_fits();
106 case_bytes_are_all_or_nothing();
107 case_bytes_do_not_terminate();
108}
diff --git a/tests/test.h b/tests/test.h
index aa4eb00..0eddc82 100644
--- a/tests/test.h
+++ b/tests/test.h
@@ -90,5 +90,6 @@ void test_mutex(void);
90void test_batch(void); 90void test_batch(void);
91void test_fdstate(void); 91void test_fdstate(void);
92void test_event(void); 92void test_event(void);
93void test_fmt(void);
93 94
94#endif 95#endif
diff --git a/trackerlogic.c b/trackerlogic.c
index 9705bd6..1f38f07 100644
--- a/trackerlogic.c
+++ b/trackerlogic.c
@@ -14,6 +14,7 @@
14/* Opentracker */ 14/* Opentracker */
15#include "ot_accesslist.h" 15#include "ot_accesslist.h"
16#include "ot_clean.h" 16#include "ot_clean.h"
17#include "ot_fmt.h"
17#include "ot_fullscrape.h" 18#include "ot_fullscrape.h"
18#include "ot_http.h" 19#include "ot_http.h"
19#include "ot_ip.h" 20#include "ot_ip.h"
@@ -299,11 +300,12 @@ static size_t return_peers_for_torrent_udp(struct ot_workstruct *ws, ot_torrent
299} 300}
300 301
301static size_t return_peers_for_torrent_tcp(struct ot_workstruct *ws, ot_torrent *torrent, size_t amount, char *reply) { 302static size_t return_peers_for_torrent_tcp(struct ot_workstruct *ws, ot_torrent *torrent, size_t amount, char *reply) {
302 char *r = reply; 303 char *r = reply;
303 int erval = OT_CLIENT_REQUEST_INTERVAL_RANDOM; 304 const char *r_end = ws->outbuf + G_OUTBUF_SIZE;
304 size_t seed_count = torrent->peer_list6->seed_count + torrent->peer_list4->seed_count; 305 int erval = OT_CLIENT_REQUEST_INTERVAL_RANDOM;
305 size_t down_count = torrent->peer_list6->down_count + torrent->peer_list4->down_count; 306 size_t seed_count = torrent->peer_list6->seed_count + torrent->peer_list4->seed_count;
306 size_t peer_count = torrent->peer_list6->peer_count + torrent->peer_list4->peer_count - seed_count; 307 size_t down_count = torrent->peer_list6->down_count + torrent->peer_list4->down_count;
308 size_t peer_count = torrent->peer_list6->peer_count + torrent->peer_list4->peer_count - seed_count;
307 309
308 /* Simple case: amount of peers in both lists is less than requested, here we return all results */ 310 /* Simple case: amount of peers in both lists is less than requested, here we return all results */
309 size_t amount_v4 = torrent->peer_list4->peer_count; 311 size_t amount_v4 = torrent->peer_list4->peer_count;
@@ -341,11 +343,11 @@ static size_t return_peers_for_torrent_tcp(struct ot_workstruct *ws, ot_torrent
341 ++amount_v4; 343 ++amount_v4;
342 } 344 }
343 345
344 r += 346 fmt_append(&r, r_end, "d8:completei%zde10:downloadedi%zde10:incompletei%zde8:intervali%ie12:min intervali%ie", seed_count, down_count, peer_count, erval,
345 sprintf(r, "d8:completei%zde10:downloadedi%zde10:incompletei%zde8:intervali%ie12:min intervali%ie", seed_count, down_count, peer_count, erval, erval / 2); 347 erval / 2);
346 348
347 if (amount_v4) { 349 if (amount_v4) {
348 r += sprintf(r, PEERS_BENCODED4 "%zd:", OT_PEER_COMPARE_SIZE4 * amount_v4); 350 fmt_append(&r, r_end, PEERS_BENCODED4 "%zd:", OT_PEER_COMPARE_SIZE4 * amount_v4);
349 if (amount_v4 == torrent->peer_list4->peer_count) 351 if (amount_v4 == torrent->peer_list4->peer_count)
350 r += return_peers_all(torrent->peer_list4, OT_PEER_SIZE4, r); 352 r += return_peers_all(torrent->peer_list4, OT_PEER_SIZE4, r);
351 else 353 else
@@ -353,14 +355,14 @@ static size_t return_peers_for_torrent_tcp(struct ot_workstruct *ws, ot_torrent
353 } 355 }
354 356
355 if (amount_v6) { 357 if (amount_v6) {
356 r += sprintf(r, PEERS_BENCODED6 "%zd:", OT_PEER_COMPARE_SIZE6 * amount_v6); 358 fmt_append(&r, r_end, PEERS_BENCODED6 "%zd:", OT_PEER_COMPARE_SIZE6 * amount_v6);
357 if (amount_v6 == torrent->peer_list6->peer_count) 359 if (amount_v6 == torrent->peer_list6->peer_count)
358 r += return_peers_all(torrent->peer_list6, OT_PEER_SIZE6, r); 360 r += return_peers_all(torrent->peer_list6, OT_PEER_SIZE6, r);
359 else 361 else
360 r += return_peers_selection(ws, torrent->peer_list6, OT_PEER_SIZE6, amount_v6, r); 362 r += return_peers_selection(ws, torrent->peer_list6, OT_PEER_SIZE6, amount_v6, r);
361 } 363 }
362 364
363 *r++ = 'e'; 365 fmt_append(&r, r_end, "e");
364 366
365 return r - reply; 367 return r - reply;
366} 368}
@@ -403,12 +405,11 @@ size_t return_udp_scrape_for_torrent(ot_hash const hash, char *reply) {
403} 405}
404 406
405/* Fetches scrape info for a specific torrent */ 407/* Fetches scrape info for a specific torrent */
406size_t return_tcp_scrape_for_torrent(ot_hash const *hash_list, size_t amount, char *reply) { 408void return_tcp_scrape_for_torrent(ot_hash const *hash_list, size_t amount, char **reply, const char *reply_end) {
407 char *r = reply; 409 int exactmatch;
408 int exactmatch;
409 size_t i; 410 size_t i;
410 411
411 r += sprintf(r, "d5:filesd"); 412 fmt_append(reply, reply_end, "d5:filesd");
412 413
413 for (i = 0; i < amount; ++i) { 414 for (i = 0; i < amount; ++i) {
414 int delta_torrentcount = 0; 415 int delta_torrentcount = 0;
@@ -421,22 +422,17 @@ size_t return_tcp_scrape_for_torrent(ot_hash const *hash_list, size_t amount, ch
421 vector_remove_torrent(torrents_list, torrent); 422 vector_remove_torrent(torrents_list, torrent);
422 delta_torrentcount = -1; 423 delta_torrentcount = -1;
423 } else { 424 } else {
424 *r++ = '2'; 425 fmt_append(reply, reply_end, "20:");
425 *r++ = '0'; 426 fmt_bytes(reply, reply_end, hash, sizeof(ot_hash));
426 *r++ = ':'; 427 fmt_append(reply, reply_end, "d8:completei%zde10:downloadedi%zde10:incompletei%zdee",
427 memcpy(r, hash, sizeof(ot_hash)); 428 torrent->peer_list6->seed_count + torrent->peer_list4->seed_count, torrent->peer_list6->down_count + torrent->peer_list4->down_count,
428 r += sizeof(ot_hash); 429 torrent->peer_list6->peer_count + torrent->peer_list4->peer_count - torrent->peer_list6->seed_count - torrent->peer_list4->seed_count);
429 r += sprintf(r, "d8:completei%zde10:downloadedi%zde10:incompletei%zdee", torrent->peer_list6->seed_count + torrent->peer_list4->seed_count,
430 torrent->peer_list6->down_count + torrent->peer_list4->down_count,
431 torrent->peer_list6->peer_count + torrent->peer_list4->peer_count - torrent->peer_list6->seed_count - torrent->peer_list4->seed_count);
432 } 430 }
433 } 431 }
434 mutex_bucket_unlock_by_hash(*hash, delta_torrentcount); 432 mutex_bucket_unlock_by_hash(*hash, delta_torrentcount);
435 } 433 }
436 434
437 *r++ = 'e'; 435 fmt_append(reply, reply_end, "ee");
438 *r++ = 'e';
439 return r - reply;
440} 436}
441 437
442static ot_peerlist dummy_list; 438static ot_peerlist dummy_list;
@@ -472,9 +468,12 @@ size_t remove_peer_from_torrent(PROTO_FLAG proto, struct ot_workstruct *ws) {
472 } 468 }
473 469
474 if (proto == FLAG_TCP) { 470 if (proto == FLAG_TCP) {
475 int erval = OT_CLIENT_REQUEST_INTERVAL_RANDOM; 471 int erval = OT_CLIENT_REQUEST_INTERVAL_RANDOM;
476 ws->reply_size = sprintf(ws->reply, "d8:completei%zde10:incompletei%zde8:intervali%ie12:min intervali%ie%s0:e", seed_count, peer_count - seed_count, erval, 472 char *r = ws->reply;
477 erval / 2, peer_size == OT_PEER_SIZE6 ? PEERS_BENCODED6 : PEERS_BENCODED4); 473
474 fmt_append(&r, ws->outbuf + G_OUTBUF_SIZE, "d8:completei%zde10:incompletei%zde8:intervali%ie12:min intervali%ie%s0:e", seed_count, peer_count - seed_count,
475 erval, erval / 2, peer_size == OT_PEER_SIZE6 ? PEERS_BENCODED6 : PEERS_BENCODED4);
476 ws->reply_size = r - ws->reply;
478 } 477 }
479 478
480 /* Handle UDP reply */ 479 /* Handle UDP reply */
diff --git a/trackerlogic.h b/trackerlogic.h
index 42018c3..63abf78 100644
--- a/trackerlogic.h
+++ b/trackerlogic.h
@@ -187,7 +187,7 @@ void exerr(const char *message);
187 otherwise it is released in return_peers_for_torrent */ 187 otherwise it is released in return_peers_for_torrent */
188size_t add_peer_to_torrent_and_return_peers(PROTO_FLAG proto, struct ot_workstruct *ws, size_t amount); 188size_t add_peer_to_torrent_and_return_peers(PROTO_FLAG proto, struct ot_workstruct *ws, size_t amount);
189size_t remove_peer_from_torrent(PROTO_FLAG proto, struct ot_workstruct *ws); 189size_t remove_peer_from_torrent(PROTO_FLAG proto, struct ot_workstruct *ws);
190size_t return_tcp_scrape_for_torrent(ot_hash const *hash_list, size_t amount, char *reply); 190void return_tcp_scrape_for_torrent(ot_hash const *hash_list, size_t amount, char **reply, const char *reply_end);
191size_t return_udp_scrape_for_torrent(ot_hash const hash, char *reply); 191size_t return_udp_scrape_for_torrent(ot_hash const hash, char *reply);
192void add_torrent_from_saved_state(ot_hash const hash, ot_time base, size_t down_count); 192void add_torrent_from_saved_state(ot_hash const hash, ot_time base, size_t down_count);
193#ifdef _DEBUG_RANDOMTORRENTS 193#ifdef _DEBUG_RANDOMTORRENTS