From 1f9494979268b62ed4b4588fb7d9e790c0e12100 Mon Sep 17 00:00:00 2001 From: Dirk Engling Date: Mon, 26 Oct 2026 22:32:52 +0100 Subject: 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 --- Makefile | 8 +- ot_config.c | 7 +- ot_fmt.c | 42 ++++++++ ot_fmt.h | 21 ++++ ot_fullscrape.c | 38 ++++--- ot_http.c | 40 ++++++-- ot_stats.c | 304 ++++++++++++++++++++++++++++---------------------------- ot_stats.h | 4 +- ot_sync.c | 13 +-- tests/runner.c | 1 + tests/t_fmt.c | 108 ++++++++++++++++++++ tests/test.h | 1 + trackerlogic.c | 55 +++++----- trackerlogic.h | 2 +- 14 files changed, 419 insertions(+), 225 deletions(-) create mode 100644 ot_fmt.c create mode 100644 ot_fmt.h create mode 100644 tests/t_fmt.c diff --git a/Makefile b/Makefile index d74742a..a884c2c 100644 --- a/Makefile +++ b/Makefile @@ -69,8 +69,8 @@ CFLAGS_libowfat=-I$(LIBOWFAT_HEADERS) LDFLAGS_libowfat=-L$(LIBOWFAT_LIBRARY) -lowfat BINARY =opentracker -HEADERS=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 -SOURCES=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 +HEADERS=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 +SOURCES=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 SOURCES_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 OBJECTS = $(SOURCES:%.c=%.o) @@ -120,8 +120,8 @@ $(OBJECTS_proxy_debug): $(HEADERS) # has to. tests/stubs.c supplies the few symbols those modules reference but # the tests never reach. TESTBIN=tests/runner -TEST_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 -TEST_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 +TEST_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 +TEST_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 TEST_HEADERS=tests/test.h $(HEADERS) # Tests reach code that sits behind feature flags, so the units under test are # 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) { help(argv[0]); exit(0); case 'v': { - char buffer[8192]; - stats_return_tracker_version(buffer); + char buffer[8192]; + char *cursor = buffer; + + stats_return_tracker_version(&cursor, buffer + sizeof(buffer) - 1); + *cursor = 0; fputs(buffer, stderr); exit(0); } 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 @@ +/* This software was written by Dirk Engling + It is considered beerware. Prost. Skol. Cheers or whatever. + + $id$ */ + +/* System */ +#include +#include +#include + +/* Opentracker */ +#include "ot_fmt.h" + +void fmt_append(char **cursor, const char *end, const char *format, ...) { + va_list arguments; + size_t room; + int written; + + if (*cursor >= end) { + *cursor = (char *)end; + return; + } + + room = (size_t)(end - *cursor); + va_start(arguments, format); + written = vsnprintf(*cursor, room, format, arguments); + va_end(arguments); + + if (written < 0) + return; + *cursor = (size_t)written >= room ? (char *)end : *cursor + written; +} + +void fmt_bytes(char **cursor, const char *end, const void *bytes, size_t length) { + if (*cursor >= end || (size_t)(end - *cursor) < length) { + *cursor = (char *)end; + return; + } + + memcpy(*cursor, bytes, length); + *cursor += length; +} 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 @@ +/* This software was written by Dirk Engling + It is considered beerware. Prost. Skol. Cheers or whatever. + + $id$ */ + +#ifndef OT_FMT_H__ +#define OT_FMT_H__ + +#include + +/* Appending to a cursor that walks a fixed buffer. Both take the cursor by + reference and advance it, so a reply is written as a run of statements and + no caller does pointer arithmetic of its own. */ + +void fmt_append(char **cursor, const char *end, const char *format, ...) __attribute__((format(printf, 3, 4))); + +/* Append raw bytes for the binary stretches of a bencoded reply. + Either everything is appended or nothing at all */ +void fmt_bytes(char **cursor, const char *end, const void *bytes, size_t length); + +#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 @@ #endif /* Opentracker */ +#include "ot_fmt.h" #include "ot_hex.h" #include "ot_fullscrape.h" #include "ot_iovec.h" @@ -83,10 +84,15 @@ void fullscrape_deliver( int64_t sock, ot_tasktype tasktype ) { mutex_workqueue_pushtask( sock, TASK_FULLSCRAPE | tasktype ); } -static char * fullscrape_write_one( ot_tasktype mode, char *r, ot_torrent *torrent, ot_hash *hash ) { - size_t seed_count = torrent->peer_list6->seed_count + torrent->peer_list4->seed_count; - size_t peer_count = torrent->peer_list6->peer_count + torrent->peer_list4->peer_count; - size_t down_count = torrent->peer_list6->down_count + torrent->peer_list4->down_count; +static void fullscrape_write_one( ot_tasktype mode, char **cursor, ot_torrent *torrent, ot_hash *hash ) { + /* Callers guarantee this much room: the chunk writer keeps its low + watermark that far from the end, the compressors hand over a buffer of + exactly that size */ + char *r = *cursor; + const char *r_end = r + OT_SCRAPE_MAXENTRYLEN; + size_t seed_count = torrent->peer_list6->seed_count + torrent->peer_list4->seed_count; + size_t peer_count = torrent->peer_list6->peer_count + torrent->peer_list4->peer_count; + size_t down_count = torrent->peer_list6->down_count + torrent->peer_list4->down_count; switch (mode & TASK_TASK_MASK) { case TASK_FULLSCRAPE: @@ -98,18 +104,18 @@ static char * fullscrape_write_one( ot_tasktype mode, char *r, ot_torrent *torre memcpy(r, hash, sizeof(ot_hash)); r += sizeof(ot_hash); /* push rest of the scrape string */ - r += sprintf(r, "d8:completei%zde10:downloadedi%zde10:incompletei%zdee", seed_count, down_count, peer_count - seed_count); + fmt_append(&r, r_end, "d8:completei%zde10:downloadedi%zde10:incompletei%zdee", seed_count, down_count, peer_count - seed_count); break; case TASK_FULLSCRAPE_TPB_ASCII: hex_from_hash(r, *hash); r += 2 * sizeof(ot_hash); - r += sprintf(r, ":%zd:%zd\n", seed_count, peer_count - seed_count); + fmt_append(&r, r_end, ":%zd:%zd\n", seed_count, peer_count - seed_count); break; case TASK_FULLSCRAPE_TPB_ASCII_PLUS: hex_from_hash(r, *hash); r += 2 * sizeof(ot_hash); - r += sprintf(r, ":%zd:%zd:%zd\n", seed_count, peer_count - seed_count, down_count); + fmt_append(&r, r_end, ":%zd:%zd:%zd\n", seed_count, peer_count - seed_count, down_count); break; case TASK_FULLSCRAPE_TPB_BINARY: memcpy(r, *hash, sizeof(ot_hash)); @@ -120,15 +126,15 @@ static char * fullscrape_write_one( ot_tasktype mode, char *r, ot_torrent *torre break; case TASK_FULLSCRAPE_TPB_URLENCODED: r += urlencoded_format(r, *hash, sizeof(ot_hash)); - r += sprintf(r, ":%zd:%zd\n", seed_count, peer_count - seed_count); + fmt_append(&r, r_end, ":%zd:%zd\n", seed_count, peer_count - seed_count); break; case TASK_FULLSCRAPE_TRACKERSTATE: hex_from_hash(r, *hash); r += 2 * sizeof(ot_hash); - r += sprintf(r, ":%zd:%zd\n", torrent->peer_list6->base, down_count); + fmt_append(&r, r_end, ":%zd:%zd\n", torrent->peer_list6->base, down_count); break; } - return r; + *cursor = r; } static void fullscrape_make(int taskid, ot_tasktype mode) { @@ -145,7 +151,7 @@ static void fullscrape_make(int taskid, ot_tasktype mode) { re = r + OT_SCRAPE_CHUNK_SIZE - OT_SCRAPE_MAXENTRYLEN; if ((mode & TASK_TASK_MASK) == TASK_FULLSCRAPE) - r += sprintf(r, "d5:filesd"); + fmt_append(&r, (char *)iovector.iov_base + OT_SCRAPE_CHUNK_SIZE, "d5:filesd"); /* For each bucket... */ for (bucket = 0; bucket < OT_BUCKET_COUNT; ++bucket) { @@ -156,7 +162,7 @@ static void fullscrape_make(int taskid, ot_tasktype mode) { /* For each torrent in this bucket.. */ for (i = 0; i < torrents_list->size; ++i) { - r = fullscrape_write_one(mode, r, torrents + i, &torrents[i].hash); + fullscrape_write_one(mode, &r, torrents + i, &torrents[i].hash); if (r > re) { iovector.iov_len = r - (char *)iovector.iov_base; @@ -187,7 +193,7 @@ static void fullscrape_make(int taskid, ot_tasktype mode) { } if ((mode & TASK_TASK_MASK) == TASK_FULLSCRAPE) - r += sprintf(r, "ee"); + fmt_append(&r, (char *)iovector.iov_base + OT_SCRAPE_CHUNK_SIZE, "ee"); /* Send rest of data */ iovector.iov_len = r - (char *)iovector.iov_base; @@ -230,7 +236,8 @@ static void fullscrape_make_gzip(int taskid, ot_tasktype mode) { /* For each torrent in this bucket.. */ for (i = 0; i < torrents_list->size; ++i) { char compress_buffer[OT_SCRAPE_MAXENTRYLEN]; - r = fullscrape_write_one(mode, compress_buffer, torrents + i, &torrents[i].hash); + r = compress_buffer; + fullscrape_write_one(mode, &r, torrents + i, &torrents[i].hash); strm.next_in = (uint8_t *)compress_buffer; strm.avail_in = r - compress_buffer; zres = deflate(&strm, Z_NO_FLUSH); @@ -359,7 +366,8 @@ static void fullscrape_make_zstd(int taskid, ot_tasktype mode) { /* For each torrent in this bucket.. */ for (i = 0; i < torrents_list->size; ++i) { char compress_buffer[OT_SCRAPE_MAXENTRYLEN]; - r = fullscrape_write_one(mode, compress_buffer, torrents + i, &torrents[i].hash); + r = compress_buffer; + fullscrape_write_one(mode, &r, torrents + i, &torrents[i].hash); inbuf.src = compress_buffer; inbuf.size = r - compress_buffer; 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 @@ #include "ot_scan.h" #include "ot_event.h" #include "ot_fdstate.h" +#include "ot_fmt.h" #include "ot_stats.h" #include "ot_urlencoded.h" #include "trackerlogic.h" @@ -257,6 +258,7 @@ static ssize_t http_handle_stats(const int64_t sock, struct ot_workstruct *ws, c {"txtp", TASK_FULLSCRAPE_TPB_ASCII_PLUS}, {NULL, -3}}; struct http_data *cookie = fdstate_cookie(sock); + char *reply; int mode = TASK_STATS_PEERS, scanon = 1, format = 0; /* 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 } /* Simple stats can be answerred immediately */ - return ws->reply_size = return_stats_for_tracker(ws->reply, mode, 0); + reply = ws->reply; + return_stats_for_tracker(&reply, ws->outbuf + G_OUTBUF_SIZE, mode, 0); + return ws->reply_size = reply - ws->reply; } #ifdef WANT_MODEST_FULLSCRAPES @@ -417,9 +421,10 @@ static ssize_t http_handle_fullscrape(const int64_t sock, struct ot_workstruct * static ssize_t http_handle_scrape(const int64_t sock, struct ot_workstruct *ws, char *read_ptr) { static const ot_keywords keywords_scrape[] = {{"info_hash", 1}, {NULL, -3}}; - ot_hash *multiscrape_buf = (ot_hash *)ws->request; - int scanon = 1; - size_t numwant = 0; + ot_hash *multiscrape_buf = (ot_hash *)ws->request; + char *reply = ws->reply; + int scanon = 1; + size_t numwant = 0; /* This is to hack around stupid clients that send "scrape ?info_hash" */ if (read_ptr[-1] != '?') { @@ -457,7 +462,8 @@ static ssize_t http_handle_scrape(const int64_t sock, struct ot_workstruct *ws, numwant = OT_MAXMULTISCRAPE_COUNT; /* Enough for http header + whole scrape string */ - ws->reply_size = return_tcp_scrape_for_torrent((const ot_hash *)multiscrape_buf, numwant, ws->reply); + return_tcp_scrape_for_torrent((const ot_hash *)multiscrape_buf, numwant, &reply, ws->outbuf + G_OUTBUF_SIZE); + ws->reply_size = reply - ws->reply; stats_issue_event(EVENT_SCRAPE, FLAG_TCP, ws->reply_size); return ws->reply_size; } @@ -493,6 +499,16 @@ static ot_keywords keywords_announce[] = {{"port", 1}, {"left", 2}, {"event", #endif {"peer_id", 9}, {NULL, -3}}; static ot_keywords keywords_announce_event[] = {{"completed", 1}, {"stopped", 2}, {NULL, -3}}; + +/* For the replies that are one fixed string and nothing else, where the only + thing the caller wants back is how long it turned out */ +static ssize_t reply_literal(struct ot_workstruct *ws, const char *text) { + char *reply = ws->reply; + + fmt_bytes(&reply, ws->outbuf + G_OUTBUF_SIZE, text, strlen(text)); + return reply - ws->reply; +} + static ssize_t http_handle_announce(const int64_t sock, struct ot_workstruct *ws, char *read_ptr) { size_t numwant; int64_t tmp; @@ -621,7 +637,7 @@ static ssize_t http_handle_announce(const int64_t sock, struct ot_workstruct HTTPERROR_400_PARAM; if (*tmp_buf == '-') { loglist_reset(); - return ws->reply_size = sprintf(ws->reply, "Successfully removed.\n"); + return ws->reply_size = reply_literal(ws, "Successfully removed.\n"); } parsed = ip_parse(tmp_buf, net.address); if (!parsed) @@ -638,7 +654,7 @@ static ssize_t http_handle_announce(const int64_t sock, struct ot_workstruct } net.bits = bits; loglist_add_network(&net); - return ws->reply_size = sprintf(ws->reply, "Successfully added.\n"); + return ws->reply_size = reply_literal(ws, "Successfully added.\n"); //} } break; #endif @@ -661,7 +677,7 @@ static ssize_t http_handle_announce(const int64_t sock, struct ot_workstruct /* Scanned whole query string */ if (!ws->hash) - 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"); + 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"); if (OT_PEERFLAG(&ws->peer) & PEER_FLAG_STOPPED) 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) { reply_off = SUCCESS_HTTP_SIZE_OFF - snprintf(ws->outbuf, 0, "%zd", ws->reply_size); ws->reply = ws->outbuf + reply_off; - /* 2. Now we sprintf our header so that sprintf writes its terminating '\0' exactly one byte before content starts. Complete - packet size is increased by size of header plus one byte '\n', we will copy over '\0' in next step */ - 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); + /* 2. Now we write the header so that its terminating '\0' lands exactly one byte before the content starts. The bound is the + space step 1 reserved, so the '\0' falls where step 3 expects it. Complete packet size is increased by size of header plus + one byte '\n', we will copy over '\0' in next step */ + ws->reply_size += 1 + snprintf(ws->reply, (size_t)(SUCCESS_HTTP_HEADER_LENGTH - reply_off), + "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: %zd\r\n\r", ws->reply_size); /* 3. Finally we join both blocks neatly */ 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 @@ #include "ot_iovec.h" #include "ot_ip.h" #include "ot_mutex.h" +#include "ot_fmt.h" #include "ot_stats.h" #include "trackerlogic.h" @@ -184,12 +185,11 @@ static size_t stats_get_highscore_networks(stats_network_node *node, int depth, return score; } -static size_t stats_return_busy_networks(char *reply, stats_network_node *tree, size_t amount, int limit) { +static void stats_return_busy_networks(char **reply, const char *reply_end, stats_network_node *tree, size_t amount, int limit) { ot_ip6 networks[amount]; ot_ip6 node_value; size_t scores[amount]; size_t i; - char *r = reply; memset(scores, 0, sizeof(scores)); memset(networks, 0, sizeof(networks)); @@ -197,24 +197,21 @@ static size_t stats_return_busy_networks(char *reply, stats_network_node *tree, stats_get_highscore_networks(tree, 0, node_value, scores, networks, amount, limit); - r += sprintf(r, "Networks, limit /%d:\n", limit + STATS_NETWORK_NODE_BITWIDTH); + fmt_append(reply, reply_end, "Networks, limit /%d:\n", limit + STATS_NETWORK_NODE_BITWIDTH); for (i = amount; i > 0; --i) { if (scores[i - 1]) { - r += sprintf(r, "%08zd: ", scores[i - 1]); - // #ifdef WANT_V6 - r += ip_format(r, networks[i - 1]); - *r++ = '\n'; + char ip_readable[64]; + + ip_readable[ip_format(ip_readable, networks[i - 1])] = 0; + fmt_append(reply, reply_end, "%08zd: %s\n", scores[i - 1], ip_readable); } } - *r++ = '\n'; - - return r - reply; + fmt_append(reply, reply_end, "\n"); } -static size_t stats_slash24s_txt(char *reply, size_t amount) { +static void stats_slash24s_txt(char **reply, const char *reply_end, size_t amount) { stats_network_node *slash24s_network_counters_root = NULL; - char *r = reply; - size_t i, bucket, peer_size = OT_PEER_SIZE4; + size_t i, bucket, peer_size = OT_PEER_SIZE4; for (bucket = 0; bucket < OT_BUCKET_COUNT; ++bucket) { ot_vector *torrents_list = mutex_bucket_lock(bucket); @@ -241,35 +238,28 @@ static size_t stats_slash24s_txt(char *reply, size_t amount) { } mutex_bucket_unlock(bucket, 0); if (!g_opentracker_running) - goto bailout_error; + goto done; } /* The tree is built. Now analyze */ - r += stats_return_busy_networks(r, slash24s_network_counters_root, amount, STATS_NETWORK_NODE_MAXDEPTH); - r += stats_return_busy_networks(r, slash24s_network_counters_root, amount, STATS_NETWORK_NODE_LIMIT); - goto success; + stats_return_busy_networks(reply, reply_end, slash24s_network_counters_root, amount, STATS_NETWORK_NODE_MAXDEPTH); + stats_return_busy_networks(reply, reply_end, slash24s_network_counters_root, amount, STATS_NETWORK_NODE_LIMIT); + goto done; bailout_unlock: mutex_bucket_unlock(bucket, 0); -bailout_error: - r = reply; -success: +done: stats_shift_down_network_count(&slash24s_network_counters_root, 0, sizeof(int) * 8 - 1); - - return r - reply; } #ifdef WANT_SPOT_WOODPECKER static stats_network_node *stats_woodpeckers_tree; static pthread_mutex_t g_woodpeckers_mutex = PTHREAD_MUTEX_INITIALIZER; -static size_t stats_return_woodpeckers(char *reply, int amount) { - char *r = reply; - +static void stats_return_woodpeckers(char **reply, const char *reply_end, int amount) { pthread_mutex_lock(&g_woodpeckers_mutex); - r += stats_return_busy_networks(r, stats_woodpeckers_tree, amount, STATS_NETWORK_NODE_MAXDEPTH); + stats_return_busy_networks(reply, reply_end, stats_woodpeckers_tree, amount, STATS_NETWORK_NODE_MAXDEPTH); pthread_mutex_unlock(&g_woodpeckers_mutex); - return r - reply; } #endif @@ -309,10 +299,10 @@ static inline void stats_insert_into_highscore(size_t val, ot_record* list, size /* Fetches stats from tracker */ -size_t stats_top_txt(char *reply, size_t amount) { +void stats_top_txt(char **reply, const char *reply_end, size_t amount) { size_t j, idx, bucket; ot_record top100s[100], top100c[100], top100l[100]; - char *r = reply, hex_out[42]; + char hex_out[42]; if (amount > 100) amount = 100; @@ -323,7 +313,7 @@ size_t stats_top_txt(char *reply, size_t amount) { /* Iterate over complete torrent list */ for (bucket = 0; bucket < OT_BUCKET_COUNT; ++bucket) { - ot_vector *torrents_list = mutex_bucket_lock(bucket); + ot_vector *torrents_list = mutex_bucket_lock(bucket); for (j = 0; j < torrents_list->size; ++j) { ot_torrent *torrent = (ot_torrent *)(torrents_list->data) + j; 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) { } mutex_bucket_unlock(bucket, 0); if (!g_opentracker_running) - return 0; + return; } - r += sprintf(r, "Top %zd torrents by peers:\n", amount); + fmt_append(reply, reply_end, "Top %zd torrents by peers:\n", amount); for (idx = 0; idx < amount; ++idx) if (top100c[idx].val) - r += sprintf(r, "\t%zd\t%s\n", top100c[idx].val, hex_from_hash(hex_out, top100c[idx].hash)); - r += sprintf(r, "Top %zd torrents by seeds:\n", amount); + fmt_append(reply, reply_end, "\t%zd\t%s\n", top100c[idx].val, hex_from_hash(hex_out, top100c[idx].hash)); + fmt_append(reply, reply_end, "Top %zd torrents by seeds:\n", amount); for (idx = 0; idx < amount; ++idx) if (top100s[idx].val) - r += sprintf(r, "\t%zd\t%s\n", top100s[idx].val, hex_from_hash(hex_out, top100s[idx].hash)); - r += sprintf(r, "Top %zd torrents by leechers:\n", amount); + fmt_append(reply, reply_end, "\t%zd\t%s\n", top100s[idx].val, hex_from_hash(hex_out, top100s[idx].hash)); + fmt_append(reply, reply_end, "Top %zd torrents by leechers:\n", amount); for (idx = 0; idx < amount; ++idx) if (top100l[idx].val) - r += sprintf(r, "\t%zd\t%s\n", top100l[idx].val, hex_from_hash(hex_out, top100l[idx].hash)); - - return r - reply; + fmt_append(reply, reply_end, "\t%zd\t%s\n", top100l[idx].val, hex_from_hash(hex_out, top100l[idx].hash)); } static unsigned long events_per_time(unsigned long long events, time_t t) { return events / ((unsigned int)t ? (unsigned int)t : 1); } -static size_t stats_connections_mrtg(char *reply) { +static void stats_connections_mrtg(char **reply, const char *reply_end) { ot_time t = time(NULL) - ot_start_time; - return sprintf(reply, "%llu\n%llu\n%i seconds (%i hours)\nopentracker connections, %lu conns/s :: %lu success/s.", - ot_overall_tcp_connections + ot_overall_udp_connections, - ot_overall_tcp_successfulannounces + ot_overall_udp_successfulannounces + ot_overall_udp_connects, (int)t, (int)(t / 3600), - events_per_time(ot_overall_tcp_connections + ot_overall_udp_connections, t), - events_per_time(ot_overall_tcp_successfulannounces + ot_overall_udp_successfulannounces + ot_overall_udp_connects, t)); + fmt_append(reply, reply_end, "%llu\n%llu\n%i seconds (%i hours)\nopentracker connections, %lu conns/s :: %lu success/s.", + ot_overall_tcp_connections + ot_overall_udp_connections, + ot_overall_tcp_successfulannounces + ot_overall_udp_successfulannounces + ot_overall_udp_connects, (int)t, (int)(t / 3600), + events_per_time(ot_overall_tcp_connections + ot_overall_udp_connections, t), + events_per_time(ot_overall_tcp_successfulannounces + ot_overall_udp_successfulannounces + ot_overall_udp_connects, t)); } -static size_t stats_udpconnections_mrtg(char *reply) { +static void stats_udpconnections_mrtg(char **reply, const char *reply_end) { ot_time t = time(NULL) - ot_start_time; - return sprintf(reply, "%llu\n%llu\n%i seconds (%i hours)\nopentracker udp4 stats, %lu conns/s :: %lu success/s.", ot_overall_udp_connections, - ot_overall_udp_successfulannounces + ot_overall_udp_connects, (int)t, (int)(t / 3600), events_per_time(ot_overall_udp_connections, t), - events_per_time(ot_overall_udp_successfulannounces + ot_overall_udp_connects, t)); + 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, + ot_overall_udp_successfulannounces + ot_overall_udp_connects, (int)t, (int)(t / 3600), events_per_time(ot_overall_udp_connections, t), + events_per_time(ot_overall_udp_successfulannounces + ot_overall_udp_connects, t)); } -static size_t stats_tcpconnections_mrtg(char *reply) { +static void stats_tcpconnections_mrtg(char **reply, const char *reply_end) { time_t t = time(NULL) - ot_start_time; - return sprintf(reply, "%llu\n%llu\n%i seconds (%i hours)\nopentracker tcp4 stats, %lu conns/s :: %lu success/s.", ot_overall_tcp_connections, - ot_overall_tcp_successfulannounces, (int)t, (int)(t / 3600), events_per_time(ot_overall_tcp_connections, t), - events_per_time(ot_overall_tcp_successfulannounces, t)); + 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, + ot_overall_tcp_successfulannounces, (int)t, (int)(t / 3600), events_per_time(ot_overall_tcp_connections, t), + events_per_time(ot_overall_tcp_successfulannounces, t)); } -static size_t stats_scrape_mrtg(char *reply) { +static void stats_scrape_mrtg(char **reply, const char *reply_end) { time_t t = time(NULL) - ot_start_time; - return sprintf(reply, "%llu\n%llu\n%i seconds (%i hours)\nopentracker scrape stats, %lu scrape/s (tcp and udp)", ot_overall_tcp_successfulscrapes, - ot_overall_udp_successfulscrapes, (int)t, (int)(t / 3600), - events_per_time((ot_overall_tcp_successfulscrapes + ot_overall_udp_successfulscrapes), t)); + fmt_append(reply, reply_end, "%llu\n%llu\n%i seconds (%i hours)\nopentracker scrape stats, %lu scrape/s (tcp and udp)", + ot_overall_tcp_successfulscrapes, ot_overall_udp_successfulscrapes, (int)t, (int)(t / 3600), + events_per_time((ot_overall_tcp_successfulscrapes + ot_overall_udp_successfulscrapes), t)); } -static size_t stats_fullscrapes_mrtg(char *reply) { +static void stats_fullscrapes_mrtg(char **reply, const char *reply_end) { ot_time t = time(NULL) - ot_start_time; - 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, - 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)); + fmt_append(reply, reply_end, "%llu\n%llu\n%i seconds (%i hours)\nopentracker full scrape stats, %lu conns/s :: %lu bytes/s.", + ot_full_scrape_count * 1000, 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)); } -static size_t stats_peers_mrtg(char *reply) { +static void stats_peers_mrtg(char **reply, const char *reply_end) { torrent_stats stats = {0, 0, 0}; iterate_all_torrents(torrent_statter, (uintptr_t)&stats); - return sprintf(reply, "%llu\n%llu\nopentracker serving %llu torrents\nopentracker", stats.peer_count, stats.seed_count, stats.torrent_count); + fmt_append(reply, reply_end, "%llu\n%llu\nopentracker serving %llu torrents\nopentracker", stats.peer_count, stats.seed_count, + stats.torrent_count); } -static size_t stats_torrents_mrtg(char *reply) { +static void stats_torrents_mrtg(char **reply, const char *reply_end) { size_t torrent_count = mutex_get_torrent_count(); - return sprintf(reply, "%zd\n%zd\nopentracker serving %zd torrents\nopentracker", torrent_count, (size_t)0, torrent_count); + fmt_append(reply, reply_end, "%zd\n%zd\nopentracker serving %zd torrents\nopentracker", torrent_count, (size_t)0, torrent_count); } -static size_t stats_httperrors_txt(char *reply) { - 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], - ot_failed_request_counts[1], ot_failed_request_counts[2], ot_failed_request_counts[3], ot_failed_request_counts[4], - ot_failed_request_counts[5], ot_failed_request_counts[6]); +static void stats_httperrors_txt(char **reply, const char *reply_end) { + 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", + ot_failed_request_counts[0], ot_failed_request_counts[1], ot_failed_request_counts[2], ot_failed_request_counts[3], + ot_failed_request_counts[4], ot_failed_request_counts[5], ot_failed_request_counts[6]); } -static size_t stats_return_renew_bucket(char *reply) { - char *r = reply; - int i; +static void stats_return_renew_bucket(char **reply, const char *reply_end) { + int i; for (i = 0; i < OT_PEER_TIMEOUT; ++i) - r += sprintf(r, "%02i %llu\n", i, ot_renewed[i]); - return r - reply; + fmt_append(reply, reply_end, "%02i %llu\n", i, ot_renewed[i]); } -static size_t stats_return_sync_mrtg(char *reply) { +static void stats_return_sync_mrtg(char **reply, const char *reply_end) { ot_time t = time(NULL) - ot_start_time; - 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, - (int)(t / 3600), events_per_time(ot_overall_tcp_connections + ot_overall_udp_connections, t), - events_per_time(ot_overall_tcp_successfulannounces + ot_overall_udp_successfulannounces + ot_overall_udp_connects, t)); + 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, + (int)t, (int)(t / 3600), events_per_time(ot_overall_tcp_connections + ot_overall_udp_connections, t), + events_per_time(ot_overall_tcp_successfulannounces + ot_overall_udp_successfulannounces + ot_overall_udp_connects, t)); } -static size_t stats_return_completed_mrtg(char *reply) { +static void stats_return_completed_mrtg(char **reply, const char *reply_end) { ot_time t = time(NULL) - ot_start_time; - return sprintf(reply, "%llu\n%llu\n%i seconds (%i hours)\nopentracker, %lu completed/h.", ot_overall_completed, 0LL, (int)t, (int)(t / 3600), - events_per_time(ot_overall_completed, t / 3600)); + 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), + events_per_time(ot_overall_completed, t / 3600)); } #ifdef WANT_LOG_NUMWANT extern unsigned long long numwants[201]; -static size_t stats_return_numwants(char *reply) { - char *r = reply; - int i; +static void stats_return_numwants(char **reply, const char *reply_end) { + int i; for (i = 0; i <= 200; ++i) - r += sprintf(r, "%03d => %lld\n", i, numwants[i]); - return r - reply; + fmt_append(reply, reply_end, "%03d => %lld\n", i, numwants[i]); } #endif @@ -461,17 +447,14 @@ static void stats_return_fulllog(size_t *iovec_entries, struct iovec **iovector, return; re = r + 32 * OT_STATS_TMPSIZE; } - r += sprintf(r, "%08ld: ", loglist->time); - r += ip_format(r, loglist->ip); - *r++ = '\n'; - memcpy(r, loglist->data, loglist->size); - r += loglist->size; - *r++ = '\n'; - *r++ = '*'; - *r++ = '\n'; - *r++ = '\n'; - - llnext = loglist->next; + char ip_readable[64]; + + ip_readable[ip_format(ip_readable, loglist->ip)] = 0; + fmt_append(&r, re, "%08ld: %s\n", loglist->time, ip_readable); + fmt_bytes(&r, re, loglist->data, loglist->size); + fmt_append(&r, re, "\n*\n\n"); + + llnext = loglist->next; free(loglist->data); free(loglist); loglist = llnext; @@ -480,124 +463,137 @@ static void stats_return_fulllog(size_t *iovec_entries, struct iovec **iovector, } #endif -static size_t stats_return_everything(char *reply) { +static void stats_return_everything(char **reply, const char *reply_end) { torrent_stats stats = {0, 0, 0}; int i; - char *r = reply; iterate_all_torrents(torrent_statter, (uintptr_t)&stats); - r += sprintf(r, "\n"); - r += sprintf(r, "\n"); - r += sprintf(r, " %" PRIu32 "\n", g_tracker_id); - r += sprintf(r, " \n"); - r += stats_return_tracker_version(r); - r += sprintf(r, " \n"); - r += sprintf(r, " %llu\n", (unsigned long long)(time(NULL) - ot_start_time)); - r += sprintf(r, " \n"); - r += sprintf(r, " %zd\n", mutex_get_torrent_count()); - r += sprintf(r, " %llu\n", stats.torrent_count); - r += sprintf(r, " \n"); - r += sprintf(r, " \n %llu\n \n", stats.peer_count); - r += sprintf(r, " \n %llu\n \n", stats.seed_count); - r += sprintf(r, " \n %llu\n \n", ot_overall_completed); - r += sprintf(r, " \n"); - r += sprintf(r, " \n %llu\n %llu\n %llu\n \n", - ot_overall_tcp_connections, ot_overall_tcp_successfulannounces, ot_overall_tcp_successfulscrapes); - r += sprintf(r, " \n %llu\n %llu\n %llu\n %llu\n %llu\n \n", - ot_overall_udp_connections, ot_overall_udp_connects, ot_overall_udp_successfulannounces, ot_overall_udp_successfulscrapes, - ot_overall_udp_connectionidmissmatches); - r += sprintf(r, " \n %llu\n \n", ot_overall_sync_count); - r += sprintf(r, " \n"); - r += sprintf(r, " \n"); - r += sprintf(r, " \n"); + fmt_append(reply, reply_end, "\n"); + fmt_append(reply, reply_end, "\n"); + fmt_append(reply, reply_end, " %" PRIu32 "\n", g_tracker_id); + fmt_append(reply, reply_end, " \n"); + stats_return_tracker_version(reply, reply_end); + fmt_append(reply, reply_end, " \n"); + fmt_append(reply, reply_end, " %llu\n", (unsigned long long)(time(NULL) - ot_start_time)); + fmt_append(reply, reply_end, " \n"); + fmt_append(reply, reply_end, " %zd\n", mutex_get_torrent_count()); + fmt_append(reply, reply_end, " %llu\n", stats.torrent_count); + fmt_append(reply, reply_end, " \n"); + fmt_append(reply, reply_end, " \n %llu\n \n", stats.peer_count); + fmt_append(reply, reply_end, " \n %llu\n \n", stats.seed_count); + fmt_append(reply, reply_end, " \n %llu\n \n", ot_overall_completed); + fmt_append(reply, reply_end, " \n"); + fmt_append(reply, reply_end, " \n %llu\n %llu\n %llu\n \n", + ot_overall_tcp_connections, ot_overall_tcp_successfulannounces, ot_overall_tcp_successfulscrapes); + fmt_append(reply, reply_end, " \n %llu\n %llu\n %llu\n %llu\n %llu\n \n", + ot_overall_udp_connections, ot_overall_udp_connects, ot_overall_udp_successfulannounces, ot_overall_udp_successfulscrapes, + ot_overall_udp_connectionidmissmatches); + fmt_append(reply, reply_end, " \n %llu\n \n", ot_overall_sync_count); + fmt_append(reply, reply_end, " \n"); + fmt_append(reply, reply_end, " \n"); + fmt_append(reply, reply_end, " \n"); for (i = 0; i < OT_PEER_TIMEOUT; ++i) - r += sprintf(r, " %llu\n", i, ot_renewed[i]); - r += sprintf(r, " \n"); - r += sprintf(r, " \n"); + fmt_append(reply, reply_end, " %llu\n", i, ot_renewed[i]); + fmt_append(reply, reply_end, " \n"); + fmt_append(reply, reply_end, " \n"); for (i = 0; i < CODE_HTTPERROR_COUNT; ++i) - r += sprintf(r, " %llu\n", ot_failed_request_names[i], ot_failed_request_counts[i]); - r += sprintf(r, " \n"); - r += sprintf(r, " \n %llu\n \n", ot_overall_stall_count); - r += sprintf(r, " \n"); - r += sprintf(r, ""); - return r - reply; + fmt_append(reply, reply_end, " %llu\n", ot_failed_request_names[i], ot_failed_request_counts[i]); + fmt_append(reply, reply_end, " \n"); + fmt_append(reply, reply_end, " \n %llu\n \n", ot_overall_stall_count); + fmt_append(reply, reply_end, " \n"); + fmt_append(reply, reply_end, ""); } -size_t stats_return_tracker_version(char *reply) { +void stats_return_tracker_version(char **reply, const char *reply_end) { #define QUOTE(name) #name #define SQUOTE(name) QUOTE(name) - return sprintf(reply, "https://erdgeist.org/gitweb/opentracker/commit/?id=" SQUOTE(GIT_VERSION) "\n"); + fmt_append(reply, reply_end, "https://erdgeist.org/gitweb/opentracker/commit/?id=" SQUOTE(GIT_VERSION) "\n"); } -size_t return_stats_for_tracker(char *reply, int mode, int format) { +void return_stats_for_tracker(char **reply, const char *reply_end, int mode, int format) { (void)format; switch (mode & TASK_TASK_MASK) { case TASK_STATS_CONNS: - return stats_connections_mrtg(reply); + stats_connections_mrtg(reply, reply_end); + break; case TASK_STATS_SCRAPE: - return stats_scrape_mrtg(reply); + stats_scrape_mrtg(reply, reply_end); + break; case TASK_STATS_UDP: - return stats_udpconnections_mrtg(reply); + stats_udpconnections_mrtg(reply, reply_end); + break; case TASK_STATS_TCP: - return stats_tcpconnections_mrtg(reply); + stats_tcpconnections_mrtg(reply, reply_end); + break; case TASK_STATS_FULLSCRAPE: - return stats_fullscrapes_mrtg(reply); + stats_fullscrapes_mrtg(reply, reply_end); + break; case TASK_STATS_COMPLETED: - return stats_return_completed_mrtg(reply); + stats_return_completed_mrtg(reply, reply_end); + break; case TASK_STATS_HTTPERRORS: - return stats_httperrors_txt(reply); + stats_httperrors_txt(reply, reply_end); + break; case TASK_STATS_VERSION: - return stats_return_tracker_version(reply); + stats_return_tracker_version(reply, reply_end); + break; case TASK_STATS_RENEW: - return stats_return_renew_bucket(reply); + stats_return_renew_bucket(reply, reply_end); + break; case TASK_STATS_SYNCS: - return stats_return_sync_mrtg(reply); + stats_return_sync_mrtg(reply, reply_end); + break; #ifdef WANT_LOG_NUMWANT case TASK_STATS_NUMWANTS: - return stats_return_numwants(reply); + stats_return_numwants(reply, reply_end); + break; #endif default: - return 0; + break; } } static void stats_make(size_t *iovec_entries, struct iovec **iovector, ot_tasktype mode) { - char *r; + char *r; + const char *r_end; *iovec_entries = 0; *iovector = NULL; if (!(r = iovec_new_chunk(iovec_entries, iovector, OT_STATS_TMPSIZE))) return; + r_end = r + OT_STATS_TMPSIZE; switch (mode & TASK_TASK_MASK) { case TASK_STATS_TORRENTS: - r += stats_torrents_mrtg(r); + stats_torrents_mrtg(&r, r_end); break; case TASK_STATS_PEERS: - r += stats_peers_mrtg(r); + stats_peers_mrtg(&r, r_end); break; case TASK_STATS_SLASH24S: - r += stats_slash24s_txt(r, 128); + stats_slash24s_txt(&r, r_end, 128); break; case TASK_STATS_TOP10: - r += stats_top_txt(r, 10); + stats_top_txt(&r, r_end, 10); break; case TASK_STATS_TOP100: r = iovec_next_chunk(iovec_entries, iovector, r, 4 * OT_STATS_TMPSIZE); if (!r) return; - r += stats_top_txt(r, 100); + r_end = r + 4 * OT_STATS_TMPSIZE; + stats_top_txt(&r, r_end, 100); break; case TASK_STATS_EVERYTHING: r = iovec_next_chunk(iovec_entries, iovector, r, OT_STATS_TMPSIZE + 64 * OT_PEER_TIMEOUT); if (!r) return; - r += stats_return_everything(r); + r_end = r + OT_STATS_TMPSIZE + 64 * OT_PEER_TIMEOUT; + stats_return_everything(&r, r_end); break; #ifdef WANT_SPOT_WOODPECKER case TASK_STATS_WOODPECKERS: - r += stats_return_woodpeckers(r, 128); + stats_return_woodpeckers(&r, r_end, 128); break; #endif #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 { void stats_issue_event(ot_status_event event, PROTO_FLAG proto, uintptr_t event_data); void stats_deliver(int64_t sock, int tasktype); void stats_cleanup(void); -size_t return_stats_for_tracker(char *reply, int mode, int format); -size_t stats_return_tracker_version(char *reply); +void return_stats_for_tracker(char **reply, const char *reply_end, int mode, int format); +void stats_return_tracker_version(char **reply, const char *reply_end); void stats_init(void); void stats_deinit(void); 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 @@ #include /* Opentracker */ +#include "ot_fmt.h" #include "ot_iovec.h" #include "ot_mutex.h" #include "ot_scan.h" @@ -109,14 +110,10 @@ static void sync_make(size_t *iovec_entries, struct iovec **iovector) { re = r + OT_SYNC_CHUNK_SIZE; } - *r++ = '2'; - *r++ = '0'; - *r++ = ':'; - memmove(r, hash, sizeof(ot_hash)); - r += sizeof(ot_hash); - r += sprintf(r, "%zd:", byte_count); - memmove(r, peer_list->changeset.data, byte_count); - r += byte_count; + fmt_append(&r, re, "20:"); + fmt_bytes(&r, re, hash, sizeof(ot_hash)); + fmt_append(&r, re, "%zd:", byte_count); + fmt_bytes(&r, re, peer_list->changeset.data, byte_count); } /* 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}, {"ot_batch", test_batch}, {"ot_fdstate", test_fdstate}, {"ot_event", test_event}, + {"ot_fmt", test_fmt}, {"ot_accesslist", test_accesslist}, {NULL, NULL}}; 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 @@ +/* This software was written by Dirk Engling + It is considered beerware. Prost. Skol. Cheers or whatever. + + $id$ */ + +/* System */ +#include + +/* Opentracker */ +#include "ot_fmt.h" +#include "test.h" + +static void case_appends_in_sequence(void) { + char buffer[64]; + char *r = buffer, *re = buffer + sizeof(buffer); + + test_case("appends follow each other and advance the cursor"); + fmt_append(&r, re, "d8:completei%zde", (size_t)7); + fmt_append(&r, re, "ee"); + *r = 0; + CHECK_STR(buffer, "d8:completei7eee"); + CHECK_INT(r - buffer, 16); +} + +static void case_truncation_stops_at_the_end(void) { + char buffer[16]; + char *r = buffer, *re = buffer + 8; /* pretend the buffer ends early */ + + memset(buffer, '#', sizeof(buffer)); + test_case("a value too long for the room left stops at the end"); + fmt_append(&r, re, "%s", "0123456789"); + CHECK_PTR(r, re); + + test_case("and writes nothing past it"); + CHECK_INT(buffer[8], '#'); + CHECK_INT(buffer[15], '#'); +} + +static void case_a_full_buffer_takes_nothing_more(void) { + char buffer[16]; + char *r = buffer + 8, *re = buffer + 8; + + memset(buffer, '#', sizeof(buffer)); + test_case("appending to a cursor already at the end changes nothing"); + fmt_append(&r, re, "more"); + CHECK_PTR(r, re); + CHECK_INT(buffer[8], '#'); +} + +static void case_an_append_that_just_fits(void) { + char buffer[8]; + char *r = buffer, *re = buffer + 3; + + memset(buffer, '#', sizeof(buffer)); + test_case("three characters into three bytes of room still truncate"); + fmt_append(&r, re, "abc"); + CHECK_PTR(r, re); + CHECK_INT(buffer[0], 'a'); + CHECK_INT(buffer[1], 'b'); + + test_case("because snprintf spends the last byte on its terminator"); + CHECK_INT(buffer[2], 0); + CHECK_INT(buffer[3], '#'); +} + +static void case_bytes_are_all_or_nothing(void) { + char buffer[16]; + char *r = buffer, *re = buffer + 8; + + memset(buffer, '#', sizeof(buffer)); + test_case("a run of bytes is written when it fits"); + fmt_bytes(&r, re, "\0\1\2\3", 4); + CHECK_PTR(r, buffer + 4); + CHECK_MEM(buffer, "\0\1\2\3", 4); + + test_case("a run that would not fit is refused whole"); + fmt_bytes(&r, re, "abcde", 5); + CHECK_PTR(r, re); + CHECK_INT(buffer[4], '#'); + + test_case("a run of exactly the room left is written"); + r = buffer + 4; + fmt_bytes(&r, re, "wxyz", 4); + CHECK_PTR(r, re); + CHECK_MEM(buffer + 4, "wxyz", 4); + CHECK_INT(buffer[8], '#'); +} + +static void case_bytes_do_not_terminate(void) { + char buffer[8]; + char *r = buffer, *re = buffer + 4; + + memset(buffer, '#', sizeof(buffer)); + test_case("unlike an append, a run of bytes may fill the room exactly"); + fmt_bytes(&r, re, "abcd", 4); + CHECK_PTR(r, re); + CHECK_MEM(buffer, "abcd", 4); + CHECK_INT(buffer[4], '#'); +} + +void test_fmt(void) { + case_appends_in_sequence(); + case_truncation_stops_at_the_end(); + case_a_full_buffer_takes_nothing_more(); + case_an_append_that_just_fits(); + case_bytes_are_all_or_nothing(); + case_bytes_do_not_terminate(); +} 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); void test_batch(void); void test_fdstate(void); void test_event(void); +void test_fmt(void); #endif diff --git a/trackerlogic.c b/trackerlogic.c index 9705bd6..1f38f07 100644 --- a/trackerlogic.c +++ b/trackerlogic.c @@ -14,6 +14,7 @@ /* Opentracker */ #include "ot_accesslist.h" #include "ot_clean.h" +#include "ot_fmt.h" #include "ot_fullscrape.h" #include "ot_http.h" #include "ot_ip.h" @@ -299,11 +300,12 @@ static size_t return_peers_for_torrent_udp(struct ot_workstruct *ws, ot_torrent } static size_t return_peers_for_torrent_tcp(struct ot_workstruct *ws, ot_torrent *torrent, size_t amount, char *reply) { - char *r = reply; - int erval = OT_CLIENT_REQUEST_INTERVAL_RANDOM; - size_t seed_count = torrent->peer_list6->seed_count + torrent->peer_list4->seed_count; - size_t down_count = torrent->peer_list6->down_count + torrent->peer_list4->down_count; - size_t peer_count = torrent->peer_list6->peer_count + torrent->peer_list4->peer_count - seed_count; + char *r = reply; + const char *r_end = ws->outbuf + G_OUTBUF_SIZE; + int erval = OT_CLIENT_REQUEST_INTERVAL_RANDOM; + size_t seed_count = torrent->peer_list6->seed_count + torrent->peer_list4->seed_count; + size_t down_count = torrent->peer_list6->down_count + torrent->peer_list4->down_count; + size_t peer_count = torrent->peer_list6->peer_count + torrent->peer_list4->peer_count - seed_count; /* Simple case: amount of peers in both lists is less than requested, here we return all results */ 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 ++amount_v4; } - r += - sprintf(r, "d8:completei%zde10:downloadedi%zde10:incompletei%zde8:intervali%ie12:min intervali%ie", seed_count, down_count, peer_count, erval, erval / 2); + fmt_append(&r, r_end, "d8:completei%zde10:downloadedi%zde10:incompletei%zde8:intervali%ie12:min intervali%ie", seed_count, down_count, peer_count, erval, + erval / 2); if (amount_v4) { - r += sprintf(r, PEERS_BENCODED4 "%zd:", OT_PEER_COMPARE_SIZE4 * amount_v4); + fmt_append(&r, r_end, PEERS_BENCODED4 "%zd:", OT_PEER_COMPARE_SIZE4 * amount_v4); if (amount_v4 == torrent->peer_list4->peer_count) r += return_peers_all(torrent->peer_list4, OT_PEER_SIZE4, r); else @@ -353,14 +355,14 @@ static size_t return_peers_for_torrent_tcp(struct ot_workstruct *ws, ot_torrent } if (amount_v6) { - r += sprintf(r, PEERS_BENCODED6 "%zd:", OT_PEER_COMPARE_SIZE6 * amount_v6); + fmt_append(&r, r_end, PEERS_BENCODED6 "%zd:", OT_PEER_COMPARE_SIZE6 * amount_v6); if (amount_v6 == torrent->peer_list6->peer_count) r += return_peers_all(torrent->peer_list6, OT_PEER_SIZE6, r); else r += return_peers_selection(ws, torrent->peer_list6, OT_PEER_SIZE6, amount_v6, r); } - *r++ = 'e'; + fmt_append(&r, r_end, "e"); return r - reply; } @@ -403,12 +405,11 @@ size_t return_udp_scrape_for_torrent(ot_hash const hash, char *reply) { } /* Fetches scrape info for a specific torrent */ -size_t return_tcp_scrape_for_torrent(ot_hash const *hash_list, size_t amount, char *reply) { - char *r = reply; - int exactmatch; +void return_tcp_scrape_for_torrent(ot_hash const *hash_list, size_t amount, char **reply, const char *reply_end) { + int exactmatch; size_t i; - r += sprintf(r, "d5:filesd"); + fmt_append(reply, reply_end, "d5:filesd"); for (i = 0; i < amount; ++i) { int delta_torrentcount = 0; @@ -421,22 +422,17 @@ size_t return_tcp_scrape_for_torrent(ot_hash const *hash_list, size_t amount, ch vector_remove_torrent(torrents_list, torrent); delta_torrentcount = -1; } else { - *r++ = '2'; - *r++ = '0'; - *r++ = ':'; - memcpy(r, hash, sizeof(ot_hash)); - r += sizeof(ot_hash); - r += sprintf(r, "d8:completei%zde10:downloadedi%zde10:incompletei%zdee", torrent->peer_list6->seed_count + torrent->peer_list4->seed_count, - torrent->peer_list6->down_count + torrent->peer_list4->down_count, - torrent->peer_list6->peer_count + torrent->peer_list4->peer_count - torrent->peer_list6->seed_count - torrent->peer_list4->seed_count); + fmt_append(reply, reply_end, "20:"); + fmt_bytes(reply, reply_end, hash, sizeof(ot_hash)); + fmt_append(reply, reply_end, "d8:completei%zde10:downloadedi%zde10:incompletei%zdee", + torrent->peer_list6->seed_count + torrent->peer_list4->seed_count, torrent->peer_list6->down_count + torrent->peer_list4->down_count, + torrent->peer_list6->peer_count + torrent->peer_list4->peer_count - torrent->peer_list6->seed_count - torrent->peer_list4->seed_count); } } mutex_bucket_unlock_by_hash(*hash, delta_torrentcount); } - *r++ = 'e'; - *r++ = 'e'; - return r - reply; + fmt_append(reply, reply_end, "ee"); } static ot_peerlist dummy_list; @@ -472,9 +468,12 @@ size_t remove_peer_from_torrent(PROTO_FLAG proto, struct ot_workstruct *ws) { } if (proto == FLAG_TCP) { - int erval = OT_CLIENT_REQUEST_INTERVAL_RANDOM; - 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, - erval / 2, peer_size == OT_PEER_SIZE6 ? PEERS_BENCODED6 : PEERS_BENCODED4); + int erval = OT_CLIENT_REQUEST_INTERVAL_RANDOM; + char *r = ws->reply; + + 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, + erval, erval / 2, peer_size == OT_PEER_SIZE6 ? PEERS_BENCODED6 : PEERS_BENCODED4); + ws->reply_size = r - ws->reply; } /* 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); otherwise it is released in return_peers_for_torrent */ size_t add_peer_to_torrent_and_return_peers(PROTO_FLAG proto, struct ot_workstruct *ws, size_t amount); size_t remove_peer_from_torrent(PROTO_FLAG proto, struct ot_workstruct *ws); -size_t return_tcp_scrape_for_torrent(ot_hash const *hash_list, size_t amount, char *reply); +void return_tcp_scrape_for_torrent(ot_hash const *hash_list, size_t amount, char **reply, const char *reply_end); size_t return_udp_scrape_for_torrent(ot_hash const hash, char *reply); void add_torrent_from_saved_state(ot_hash const hash, ot_time base, size_t down_count); #ifdef _DEBUG_RANDOMTORRENTS -- cgit v1.3