diff options
| -rw-r--r-- | Makefile | 10 | ||||
| -rw-r--r-- | opentracker.c | 30 | ||||
| -rw-r--r-- | ot_batch.c | 158 | ||||
| -rw-r--r-- | ot_batch.h | 48 | ||||
| -rw-r--r-- | ot_http.c | 53 | ||||
| -rw-r--r-- | ot_http.h | 7 | ||||
| -rw-r--r-- | proxy.c | 14 | ||||
| -rw-r--r-- | tests/runner.c | 1 | ||||
| -rw-r--r-- | tests/t_batch.c | 348 | ||||
| -rw-r--r-- | tests/test.h | 1 | ||||
| -rw-r--r-- | trackerlogic.c | 3 |
11 files changed, 591 insertions, 82 deletions
| @@ -68,9 +68,9 @@ CFLAGS+=-I$(LIBOWFAT_HEADERS) -DGIT_VERSION=$(GIT_VERSION) -Wall -pipe -pthread | |||
| 68 | LDFLAGS+=-L$(LIBOWFAT_LIBRARY) -lowfat -pthread | 68 | LDFLAGS+=-L$(LIBOWFAT_LIBRARY) -lowfat -pthread |
| 69 | 69 | ||
| 70 | BINARY =opentracker | 70 | BINARY =opentracker |
| 71 | HEADERS=trackerlogic.h scan_urlencoded_query.h ot_buf.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 | 71 | HEADERS=trackerlogic.h scan_urlencoded_query.h ot_batch.h ot_buf.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 |
| 72 | SOURCES=opentracker.c trackerlogic.c scan_urlencoded_query.c ot_buf.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 | 72 | SOURCES=opentracker.c trackerlogic.c scan_urlencoded_query.c ot_batch.c ot_buf.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 |
| 73 | SOURCES_proxy=proxy.c ot_vector.c ot_mutex.c ot_random.c ot_scan.c ot_ip.c ot_socket.c | 73 | SOURCES_proxy=proxy.c ot_batch.c ot_vector.c ot_mutex.c ot_random.c ot_scan.c ot_ip.c ot_socket.c |
| 74 | 74 | ||
| 75 | OBJECTS = $(SOURCES:%.c=%.o) | 75 | OBJECTS = $(SOURCES:%.c=%.o) |
| 76 | OBJECTS_debug = $(SOURCES:%.c=%.debug.o) | 76 | OBJECTS_debug = $(SOURCES:%.c=%.debug.o) |
| @@ -119,8 +119,8 @@ $(OBJECTS_proxy_debug): $(HEADERS) | |||
| 119 | # has to. tests/stubs.c supplies the few symbols those modules reference but | 119 | # has to. tests/stubs.c supplies the few symbols those modules reference but |
| 120 | # the tests never reach. | 120 | # the tests never reach. |
| 121 | TESTBIN=tests/runner | 121 | TESTBIN=tests/runner |
| 122 | TEST_SOURCES=tests/runner.c tests/test.c tests/stubs.c tests/t_scan_urlencoded_query.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 | 122 | TEST_SOURCES=tests/runner.c tests/test.c tests/stubs.c tests/t_scan_urlencoded_query.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 |
| 123 | TEST_UNITS=scan_urlencoded_query.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 | 123 | TEST_UNITS=scan_urlencoded_query.c ot_batch.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 |
| 124 | TEST_HEADERS=tests/test.h $(HEADERS) | 124 | TEST_HEADERS=tests/test.h $(HEADERS) |
| 125 | # Tests reach code that sits behind feature flags, so the units under test are | 125 | # Tests reach code that sits behind feature flags, so the units under test are |
| 126 | # built with those features enabled. A test binary is not a production binary. | 126 | # built with those features enabled. A test binary is not a production binary. |
diff --git a/opentracker.c b/opentracker.c index 96a265a..50fff29 100644 --- a/opentracker.c +++ b/opentracker.c | |||
| @@ -23,7 +23,6 @@ | |||
| 23 | 23 | ||
| 24 | /* Libowfat */ | 24 | /* Libowfat */ |
| 25 | #include "io.h" | 25 | #include "io.h" |
| 26 | #include "iob.h" | ||
| 27 | 26 | ||
| 28 | /* Opentracker */ | 27 | /* Opentracker */ |
| 29 | #include "ot_accesslist.h" | 28 | #include "ot_accesslist.h" |
| @@ -120,9 +119,8 @@ static ssize_t header_complete(char *request, ssize_t byte_count) { | |||
| 120 | static void handle_dead(const int64_t sock) { | 119 | static void handle_dead(const int64_t sock) { |
| 121 | struct http_data *cookie = io_getcookie(sock); | 120 | struct http_data *cookie = io_getcookie(sock); |
| 122 | if (cookie) { | 121 | if (cookie) { |
| 123 | size_t i; | 122 | if (cookie->batch) |
| 124 | for (i = 0; i < cookie->batches; ++i) | 123 | batch_reset(cookie->batch); |
| 125 | iob_reset(cookie->batch + i); | ||
| 126 | free(cookie->batch); | 124 | free(cookie->batch); |
| 127 | buf_reset(&cookie->request); | 125 | buf_reset(&cookie->request); |
| 128 | if (cookie->flag & (STRUCT_HTTP_FLAG_WAITINGFORTASK | STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER)) | 126 | if (cookie->flag & (STRUCT_HTTP_FLAG_WAITINGFORTASK | STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER)) |
| @@ -178,33 +176,23 @@ static void handle_read(const int64_t sock, struct ot_workstruct *ws) { | |||
| 178 | 176 | ||
| 179 | static void handle_write(const int64_t sock) { | 177 | static void handle_write(const int64_t sock) { |
| 180 | struct http_data *cookie = io_getcookie(sock); | 178 | struct http_data *cookie = io_getcookie(sock); |
| 181 | size_t i; | ||
| 182 | int chunked = 0; | 179 | int chunked = 0; |
| 183 | 180 | ||
| 184 | /* Look for the first io_batch still containing bytes to write */ | ||
| 185 | if (cookie) { | 181 | if (cookie) { |
| 186 | if (cookie->flag & STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER) | 182 | if (cookie->flag & STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER) |
| 187 | chunked = 1; | 183 | chunked = 1; |
| 188 | 184 | ||
| 189 | for (i = 0; i < cookie->batches; ++i) { | 185 | if (cookie->batch) { |
| 190 | if (cookie->batch[i].bytesleft) { | 186 | if (batch_send(sock, cookie->batch) == BATCH_FAILED) { |
| 191 | int64_t res = iob_send(sock, cookie->batch + i); | 187 | handle_dead(sock); |
| 192 | 188 | return; | |
| 193 | if (res == -3) { | ||
| 194 | handle_dead(sock); | ||
| 195 | return; | ||
| 196 | } | ||
| 197 | |||
| 198 | if (!cookie->batch[i].bytesleft) | ||
| 199 | continue; | ||
| 200 | |||
| 201 | if (res == -1 || res > 0 || i < cookie->batches - 1) | ||
| 202 | return; | ||
| 203 | } | 189 | } |
| 190 | if (batch_pending(cookie->batch)) | ||
| 191 | return; | ||
| 204 | } | 192 | } |
| 205 | } | 193 | } |
| 206 | 194 | ||
| 207 | /* In a chunked transfer after all batches accumulated have been sent, wait for the next one */ | 195 | /* In a chunked transfer, wait for the next piece once everything queued has gone out */ |
| 208 | if (chunked) | 196 | if (chunked) |
| 209 | io_dontwantwrite(sock); | 197 | io_dontwantwrite(sock); |
| 210 | else | 198 | else |
diff --git a/ot_batch.c b/ot_batch.c new file mode 100644 index 0000000..84eb4a8 --- /dev/null +++ b/ot_batch.c | |||
| @@ -0,0 +1,158 @@ | |||
| 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 <errno.h> | ||
| 8 | #include <limits.h> | ||
| 9 | #include <stdlib.h> | ||
| 10 | #include <string.h> | ||
| 11 | #include <sys/socket.h> | ||
| 12 | #include <sys/uio.h> | ||
| 13 | #include <unistd.h> | ||
| 14 | |||
| 15 | /* Opentracker */ | ||
| 16 | #include "ot_batch.h" | ||
| 17 | |||
| 18 | #ifndef IOV_MAX | ||
| 19 | #define IOV_MAX 1024 | ||
| 20 | #endif | ||
| 21 | |||
| 22 | #define OT_BATCH_MIN_ENTRIES 16 | ||
| 23 | |||
| 24 | static int batch_room_for(ot_batch *batch, size_t wanted) { | ||
| 25 | size_t space = batch->space ? batch->space : OT_BATCH_MIN_ENTRIES; | ||
| 26 | struct iovec *grown; | ||
| 27 | |||
| 28 | if (batch->count + wanted <= batch->space) | ||
| 29 | return 0; | ||
| 30 | |||
| 31 | /* During a chunked transfer entries are appended while earlier ones are | ||
| 32 | still going out, so the front of the array frees up as we go. Slide the | ||
| 33 | rest down before asking for more. */ | ||
| 34 | if (batch->first && batch->count - batch->first + wanted <= batch->space) { | ||
| 35 | memmove(batch->entries, batch->entries + batch->first, (batch->count - batch->first) * sizeof(struct iovec)); | ||
| 36 | batch->count -= batch->first; | ||
| 37 | batch->first = 0; | ||
| 38 | return 0; | ||
| 39 | } | ||
| 40 | |||
| 41 | while (space < batch->count + wanted) | ||
| 42 | space *= 2; | ||
| 43 | |||
| 44 | if (!(grown = realloc(batch->entries, space * sizeof(struct iovec)))) | ||
| 45 | return -1; | ||
| 46 | |||
| 47 | batch->entries = grown; | ||
| 48 | batch->space = space; | ||
| 49 | return 0; | ||
| 50 | } | ||
| 51 | |||
| 52 | int batch_add(ot_batch *batch, void *buf, size_t len) { | ||
| 53 | if (batch_room_for(batch, 1)) { | ||
| 54 | free(buf); | ||
| 55 | return -1; | ||
| 56 | } | ||
| 57 | |||
| 58 | batch->entries[batch->count].iov_base = buf; | ||
| 59 | batch->entries[batch->count].iov_len = len; | ||
| 60 | ++batch->count; | ||
| 61 | return 0; | ||
| 62 | } | ||
| 63 | |||
| 64 | int batch_add_vector(ot_batch *batch, const struct iovec *entries, size_t count) { | ||
| 65 | if (batch_room_for(batch, count)) { | ||
| 66 | while (count--) | ||
| 67 | free(entries[count].iov_base); | ||
| 68 | return -1; | ||
| 69 | } | ||
| 70 | |||
| 71 | memcpy(batch->entries + batch->count, entries, count * sizeof(struct iovec)); | ||
| 72 | batch->count += count; | ||
| 73 | return 0; | ||
| 74 | } | ||
| 75 | |||
| 76 | /* Drop everything the kernel accepted, freeing each buffer as it goes */ | ||
| 77 | static void batch_consume(ot_batch *batch, size_t written) { | ||
| 78 | while (written) { | ||
| 79 | struct iovec *entry = batch->entries + batch->first; | ||
| 80 | size_t left = entry->iov_len - batch->offset; | ||
| 81 | |||
| 82 | if (written < left) { | ||
| 83 | batch->offset += written; | ||
| 84 | return; | ||
| 85 | } | ||
| 86 | |||
| 87 | free(entry->iov_base); | ||
| 88 | written -= left; | ||
| 89 | batch->offset = 0; | ||
| 90 | ++batch->first; | ||
| 91 | } | ||
| 92 | |||
| 93 | /* Nothing is outstanding, so the array can start over from the front */ | ||
| 94 | if (batch->first == batch->count) | ||
| 95 | batch->first = batch->count = 0; | ||
| 96 | } | ||
| 97 | |||
| 98 | /* A peer that has gone away must come back as an error rather than as a | ||
| 99 | signal that kills the tracker, so ask the kernel not to raise one where it | ||
| 100 | can be asked. macOS cannot, and relies on SIGPIPE being blocked. */ | ||
| 101 | static ssize_t batch_writev(int s, struct iovec *entries, int count) { | ||
| 102 | #ifdef MSG_NOSIGNAL | ||
| 103 | struct msghdr msg; | ||
| 104 | |||
| 105 | memset(&msg, 0, sizeof(msg)); | ||
| 106 | msg.msg_iov = entries; | ||
| 107 | msg.msg_iovlen = count; | ||
| 108 | return sendmsg(s, &msg, MSG_NOSIGNAL); | ||
| 109 | #else | ||
| 110 | return writev(s, entries, count); | ||
| 111 | #endif | ||
| 112 | } | ||
| 113 | |||
| 114 | ssize_t batch_send(int s, ot_batch *batch) { | ||
| 115 | size_t entries = batch->count - batch->first; | ||
| 116 | ssize_t written; | ||
| 117 | |||
| 118 | if (!batch_pending(batch)) | ||
| 119 | return BATCH_DONE; | ||
| 120 | |||
| 121 | if (entries > IOV_MAX) | ||
| 122 | entries = IOV_MAX; | ||
| 123 | |||
| 124 | /* The first entry may be half way out already */ | ||
| 125 | batch->entries[batch->first].iov_base = (char *)batch->entries[batch->first].iov_base + batch->offset; | ||
| 126 | batch->entries[batch->first].iov_len -= batch->offset; | ||
| 127 | written = batch_writev(s, batch->entries + batch->first, (int)entries); | ||
| 128 | batch->entries[batch->first].iov_base = (char *)batch->entries[batch->first].iov_base - batch->offset; | ||
| 129 | batch->entries[batch->first].iov_len += batch->offset; | ||
| 130 | |||
| 131 | if (written < 0) | ||
| 132 | return (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) ? BATCH_AGAIN : BATCH_FAILED; | ||
| 133 | if (!written) | ||
| 134 | return BATCH_FAILED; | ||
| 135 | |||
| 136 | batch_consume(batch, (size_t)written); | ||
| 137 | return written; | ||
| 138 | } | ||
| 139 | |||
| 140 | void batch_reset(ot_batch *batch) { | ||
| 141 | while (batch->first < batch->count) | ||
| 142 | free(batch->entries[batch->first++].iov_base); | ||
| 143 | |||
| 144 | free(batch->entries); | ||
| 145 | batch->entries = NULL; | ||
| 146 | batch->count = batch->first = batch->offset = batch->space = 0; | ||
| 147 | } | ||
| 148 | |||
| 149 | /* For the few constants the chunked encoding needs, which the batch frees | ||
| 150 | along with everything else */ | ||
| 151 | int batch_add_copy(ot_batch *batch, const void *buf, size_t len) { | ||
| 152 | void *copy = malloc(len); | ||
| 153 | |||
| 154 | if (!copy) | ||
| 155 | return -1; | ||
| 156 | memcpy(copy, buf, len); | ||
| 157 | return batch_add(batch, copy, len); | ||
| 158 | } | ||
diff --git a/ot_batch.h b/ot_batch.h new file mode 100644 index 0000000..c855671 --- /dev/null +++ b/ot_batch.h | |||
| @@ -0,0 +1,48 @@ | |||
| 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_BATCH_H__ | ||
| 7 | #define OT_BATCH_H__ | ||
| 8 | |||
| 9 | #include <stddef.h> | ||
| 10 | #include <sys/types.h> | ||
| 11 | #include <sys/uio.h> | ||
| 12 | |||
| 13 | /* A queue of buffers on their way to one socket. The batch owns every buffer | ||
| 14 | handed to it and frees each one as soon as it has left, so a client that | ||
| 15 | reads slowly costs memory only for what it has not received yet. | ||
| 16 | |||
| 17 | A zeroed ot_batch is ready to use. */ | ||
| 18 | typedef struct { | ||
| 19 | struct iovec *entries; | ||
| 20 | size_t space; | ||
| 21 | size_t count; | ||
| 22 | size_t first; /* entries before this one are sent and freed */ | ||
| 23 | size_t offset; /* bytes of entries[first] already sent */ | ||
| 24 | } ot_batch; | ||
| 25 | |||
| 26 | static inline int batch_pending(const ot_batch *batch) { return batch->first != batch->count; } | ||
| 27 | |||
| 28 | /* What batch_send reports instead of a byte count */ | ||
| 29 | enum { BATCH_DONE = 0, BATCH_AGAIN = -1, BATCH_FAILED = -2 }; | ||
| 30 | |||
| 31 | /* Queue buf and take ownership of it. Returns 0, or -1 having freed buf. */ | ||
| 32 | int batch_add(ot_batch *batch, void *buf, size_t len); | ||
| 33 | |||
| 34 | /* Write as much as the socket takes. Returns the number of bytes written, or | ||
| 35 | BATCH_DONE, BATCH_AGAIN or BATCH_FAILED. */ | ||
| 36 | ssize_t batch_send(int s, ot_batch *batch); | ||
| 37 | |||
| 38 | /* Queue every entry of a vector, taking ownership of each buffer but not of | ||
| 39 | the vector itself. Returns 0, or -1 having freed all of them. */ | ||
| 40 | int batch_add_vector(ot_batch *batch, const struct iovec *entries, size_t count); | ||
| 41 | |||
| 42 | /* Queue a copy of buf, which the batch then owns. Returns 0, or -1. */ | ||
| 43 | int batch_add_copy(ot_batch *batch, const void *buf, size_t len); | ||
| 44 | |||
| 45 | /* Free whatever has not left yet, along with the batch's own array */ | ||
| 46 | void batch_reset(ot_batch *batch); | ||
| 47 | |||
| 48 | #endif | ||
| @@ -15,7 +15,7 @@ | |||
| 15 | #include <unistd.h> | 15 | #include <unistd.h> |
| 16 | 16 | ||
| 17 | /* Libowfat */ | 17 | /* Libowfat */ |
| 18 | #include "iob.h" | 18 | #include "io.h" |
| 19 | 19 | ||
| 20 | /* Opentracker */ | 20 | /* Opentracker */ |
| 21 | #include "ot_accesslist.h" | 21 | #include "ot_accesslist.h" |
| @@ -29,14 +29,7 @@ | |||
| 29 | #include "scan_urlencoded_query.h" | 29 | #include "scan_urlencoded_query.h" |
| 30 | #include "trackerlogic.h" | 30 | #include "trackerlogic.h" |
| 31 | 31 | ||
| 32 | #ifdef WANT_NO_AUTO_FREE | ||
| 33 | #define OT_IOB_INIT(B) (bzero(B, sizeof(io_batch)), 0) | ||
| 34 | #else | ||
| 35 | #define OT_IOB_INIT(B) iob_init_autofree(B, 0) | ||
| 36 | #endif | ||
| 37 | |||
| 38 | #define OT_MAXMULTISCRAPE_COUNT 64 | 32 | #define OT_MAXMULTISCRAPE_COUNT 64 |
| 39 | #define OT_BATCH_LIMIT (1024 * 1024 * 16) | ||
| 40 | extern const char *g_redirecturl; | 33 | extern const char *g_redirecturl; |
| 41 | 34 | ||
| 42 | const char *g_stats_path; | 35 | const char *g_stats_path; |
| @@ -90,20 +83,17 @@ static void http_senddata(const int64_t sock, struct ot_workstruct *ws) { | |||
| 90 | 83 | ||
| 91 | memcpy(outbuf, ws->reply + written_size, ws->reply_size - written_size); | 84 | memcpy(outbuf, ws->reply + written_size, ws->reply_size - written_size); |
| 92 | if (!cookie->batch) { | 85 | if (!cookie->batch) { |
| 93 | cookie->batch = malloc(sizeof(io_batch)); | 86 | cookie->batch = calloc(1, sizeof(ot_batch)); |
| 94 | if (!cookie->batch || OT_IOB_INIT(cookie->batch) == -1) { | 87 | if (!cookie->batch) { |
| 95 | free(cookie->batch); | ||
| 96 | free(outbuf); | 88 | free(outbuf); |
| 97 | buf_reset(&cookie->request); | 89 | buf_reset(&cookie->request); |
| 98 | free(cookie); | 90 | free(cookie); |
| 99 | io_close(sock); | 91 | io_close(sock); |
| 100 | return; | 92 | return; |
| 101 | } | 93 | } |
| 102 | |||
| 103 | cookie->batches = 1; | ||
| 104 | } | 94 | } |
| 105 | 95 | ||
| 106 | iob_addbuf_free(cookie->batch, outbuf, ws->reply_size - written_size); | 96 | batch_add(cookie->batch, outbuf, ws->reply_size - written_size); |
| 107 | 97 | ||
| 108 | /* writeable short data sockets just have a tcp timeout */ | 98 | /* writeable short data sockets just have a tcp timeout */ |
| 109 | if (!ws->keep_alive) { | 99 | if (!ws->keep_alive) { |
| @@ -146,10 +136,9 @@ ssize_t http_issue_error(const int64_t sock, struct ot_workstruct *ws, int code) | |||
| 146 | 136 | ||
| 147 | ssize_t http_sendiovecdata(const int64_t sock, struct ot_workstruct *ws, size_t iovec_entries, struct iovec *iovector, int is_partial) { | 137 | ssize_t http_sendiovecdata(const int64_t sock, struct ot_workstruct *ws, size_t iovec_entries, struct iovec *iovector, int is_partial) { |
| 148 | struct http_data *cookie = io_getcookie(sock); | 138 | struct http_data *cookie = io_getcookie(sock); |
| 149 | io_batch *current; | ||
| 150 | char *header; | 139 | char *header; |
| 151 | const char *encoding = ""; | 140 | const char *encoding = ""; |
| 152 | size_t i, header_size, size = iovec_length(&iovec_entries, (const struct iovec **)&iovector); | 141 | size_t header_size, size = iovec_length(&iovec_entries, (const struct iovec **)&iovector); |
| 153 | tai6464 t; | 142 | tai6464 t; |
| 154 | 143 | ||
| 155 | /* No cookie? Bad socket. Leave. */ | 144 | /* No cookie? Bad socket. Leave. */ |
| @@ -199,42 +188,24 @@ ssize_t http_sendiovecdata(const int64_t sock, struct ot_workstruct *ws, size_t | |||
| 199 | } | 188 | } |
| 200 | 189 | ||
| 201 | if (!cookie->batch) { | 190 | if (!cookie->batch) { |
| 202 | cookie->batch = malloc(sizeof(io_batch)); | 191 | cookie->batch = calloc(1, sizeof(ot_batch)); |
| 203 | if (!cookie->batch || OT_IOB_INIT(cookie->batch) == -1) { | 192 | if (!cookie->batch) { |
| 204 | free(cookie->batch); | ||
| 205 | free(header); | 193 | free(header); |
| 206 | iovec_free(&iovec_entries, &iovector); | 194 | iovec_free(&iovec_entries, &iovector); |
| 207 | HTTPERROR_500; | 195 | HTTPERROR_500; |
| 208 | } | 196 | } |
| 209 | cookie->batches = 1; | ||
| 210 | } | 197 | } |
| 211 | current = cookie->batch + cookie->batches - 1; | ||
| 212 | iob_addbuf_free(current, header, header_size); | ||
| 213 | |||
| 214 | /* Split huge iovectors into separate io_batches */ | ||
| 215 | for (i = 0; i < (size_t)iovec_entries; ++i) { | ||
| 216 | /* If the current batch's limit is reached, try to reallocate a new batch to work on */ | ||
| 217 | if (current->bytesleft > OT_BATCH_LIMIT) { | ||
| 218 | io_batch *new_batch = realloc(cookie->batch, (cookie->batches + 1) * sizeof(io_batch)); | ||
| 219 | 198 | ||
| 220 | if (new_batch) { | 199 | batch_add(cookie->batch, header, header_size); |
| 221 | cookie->batch = new_batch; | 200 | batch_add_vector(cookie->batch, iovector, iovec_entries); |
| 222 | if (OT_IOB_INIT(cookie->batch + cookie->batches) != -1) | ||
| 223 | ++cookie->batches; | ||
| 224 | } | ||
| 225 | /* realloc may have moved the batches, so current is stale either way */ | ||
| 226 | current = cookie->batch + cookie->batches - 1; | ||
| 227 | } | ||
| 228 | iob_addbuf_free(current, iovector[i].iov_base, iovector[i].iov_len); | ||
| 229 | } | ||
| 230 | free(iovector); | 201 | free(iovector); |
| 202 | |||
| 231 | if (cookie->flag & STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER) | 203 | if (cookie->flag & STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER) |
| 232 | iob_addbuf(current, "\r\n", 2); | 204 | batch_add_copy(cookie->batch, "\r\n", 2); |
| 233 | } | 205 | } |
| 234 | 206 | ||
| 235 | if ((cookie->flag & STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER) && cookie->batch && !is_partial) { | 207 | if ((cookie->flag & STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER) && cookie->batch && !is_partial) { |
| 236 | current = cookie->batch + cookie->batches - 1; | 208 | batch_add_copy(cookie->batch, "0\r\n\r\n", 5); |
| 237 | iob_addbuf(current, "0\r\n\r\n", 5); | ||
| 238 | cookie->flag &= ~STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER; | 209 | cookie->flag &= ~STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER; |
| 239 | } | 210 | } |
| 240 | 211 | ||
| @@ -10,9 +10,7 @@ | |||
| 10 | #include <sys/types.h> | 10 | #include <sys/types.h> |
| 11 | #include <sys/uio.h> | 11 | #include <sys/uio.h> |
| 12 | 12 | ||
| 13 | /* Libowfat */ | 13 | #include "ot_batch.h" |
| 14 | #include "iob.h" | ||
| 15 | |||
| 16 | #include "ot_buf.h" | 14 | #include "ot_buf.h" |
| 17 | #include "trackerlogic.h" | 15 | #include "trackerlogic.h" |
| 18 | 16 | ||
| @@ -27,8 +25,7 @@ typedef enum { | |||
| 27 | 25 | ||
| 28 | struct http_data { | 26 | struct http_data { |
| 29 | ot_buf request; | 27 | ot_buf request; |
| 30 | io_batch *batch; | 28 | ot_batch *batch; |
| 31 | size_t batches; | ||
| 32 | ot_ip6 ip; | 29 | ot_ip6 ip; |
| 33 | STRUCT_HTTP_FLAG flag; | 30 | STRUCT_HTTP_FLAG flag; |
| 34 | }; | 31 | }; |
| @@ -16,9 +16,9 @@ | |||
| 16 | 16 | ||
| 17 | /* Libowfat */ | 17 | /* Libowfat */ |
| 18 | #include "io.h" | 18 | #include "io.h" |
| 19 | #include "iob.h" | ||
| 20 | 19 | ||
| 21 | /* Opentracker */ | 20 | /* Opentracker */ |
| 21 | #include "ot_batch.h" | ||
| 22 | #include "ot_ip.h" | 22 | #include "ot_ip.h" |
| 23 | #include "ot_mutex.h" | 23 | #include "ot_mutex.h" |
| 24 | #include "ot_random.h" | 24 | #include "ot_random.h" |
| @@ -228,7 +228,7 @@ typedef struct { | |||
| 228 | size_t indata_length; /* Length of unprocessed data */ | 228 | size_t indata_length; /* Length of unprocessed data */ |
| 229 | uint32_t tracker_id; /* How the other end greeted */ | 229 | uint32_t tracker_id; /* How the other end greeted */ |
| 230 | int64_t fd; /* A file handle, if connected, <= 0 is disconnected (0 initially, -1 else) */ | 230 | int64_t fd; /* A file handle, if connected, <= 0 is disconnected (0 initially, -1 else) */ |
| 231 | io_batch outdata; /* The iobatch containing our sync data */ | 231 | ot_batch outdata; /* The batch containing our sync data */ |
| 232 | 232 | ||
| 233 | size_t packet_tcount; /* Number of unprocessed torrents in packet we currently receive */ | 233 | size_t packet_tcount; /* Number of unprocessed torrents in packet we currently receive */ |
| 234 | uint8_t packet_tprefix; /* Prefix byte for all torrents in current packet */ | 234 | uint8_t packet_tprefix; /* Prefix byte for all torrents in current packet */ |
| @@ -243,7 +243,7 @@ void reset_info_block(proxy_peer *peer) { | |||
| 243 | peer->tracker_id = 0; | 243 | peer->tracker_id = 0; |
| 244 | peer->fd = -1; | 244 | peer->fd = -1; |
| 245 | peer->packet_tcount = 0; | 245 | peer->packet_tcount = 0; |
| 246 | iob_reset(&peer->outdata); | 246 | batch_reset(&peer->outdata); |
| 247 | PROXYPEER_SETDISCONNECTED(peer->state); | 247 | PROXYPEER_SETDISCONNECTED(peer->state); |
| 248 | } | 248 | } |
| 249 | 249 | ||
| @@ -424,11 +424,11 @@ static void handle_write(int64_t peersocket) { | |||
| 424 | } | 424 | } |
| 425 | break; | 425 | break; |
| 426 | case FLAG_CONNECTED: | 426 | case FLAG_CONNECTED: |
| 427 | switch (iob_send(peersocket, &peer->outdata)) { | 427 | switch (batch_send(peersocket, &peer->outdata)) { |
| 428 | case 0: /* all data sent */ | 428 | case BATCH_DONE: |
| 429 | io_dontwantwrite(peersocket); | 429 | io_dontwantwrite(peersocket); |
| 430 | break; | 430 | break; |
| 431 | case -3: /* an error occured */ | 431 | case BATCH_FAILED: |
| 432 | io_close(peersocket); | 432 | io_close(peersocket); |
| 433 | reset_info_block(peer); | 433 | reset_info_block(peer); |
| 434 | break; | 434 | break; |
| @@ -728,7 +728,7 @@ static void *streamsync_worker(void *args) { | |||
| 728 | void *tmp = malloc(mem); | 728 | void *tmp = malloc(mem); |
| 729 | if (tmp) { | 729 | if (tmp) { |
| 730 | memcpy(tmp, ptr, mem); | 730 | memcpy(tmp, ptr, mem); |
| 731 | iob_addbuf_free(&g_connections[i].outdata, tmp, mem); | 731 | batch_add(&g_connections[i].outdata, tmp, mem); |
| 732 | io_wantwrite(g_connections[i].fd); | 732 | io_wantwrite(g_connections[i].fd); |
| 733 | } | 733 | } |
| 734 | } | 734 | } |
diff --git a/tests/runner.c b/tests/runner.c index 972a56a..148b928 100644 --- a/tests/runner.c +++ b/tests/runner.c | |||
| @@ -24,6 +24,7 @@ static const struct module modules[] = {{"scan_urlencoded_query", test_scan_urle | |||
| 24 | {"ot_ip", test_ip}, | 24 | {"ot_ip", test_ip}, |
| 25 | {"ot_socket", test_socket}, | 25 | {"ot_socket", test_socket}, |
| 26 | {"ot_mutex", test_mutex}, | 26 | {"ot_mutex", test_mutex}, |
| 27 | {"ot_batch", test_batch}, | ||
| 27 | {"ot_accesslist", test_accesslist}, | 28 | {"ot_accesslist", test_accesslist}, |
| 28 | {NULL, NULL}}; | 29 | {NULL, NULL}}; |
| 29 | 30 | ||
diff --git a/tests/t_batch.c b/tests/t_batch.c new file mode 100644 index 0000000..02743f1 --- /dev/null +++ b/tests/t_batch.c | |||
| @@ -0,0 +1,348 @@ | |||
| 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 <fcntl.h> | ||
| 8 | #include <poll.h> | ||
| 9 | #include <stdlib.h> | ||
| 10 | #include <string.h> | ||
| 11 | #include <sys/socket.h> | ||
| 12 | #include <unistd.h> | ||
| 13 | |||
| 14 | /* Opentracker */ | ||
| 15 | #include "ot_batch.h" | ||
| 16 | #include "test.h" | ||
| 17 | |||
| 18 | /* The batch writes to a real socket pair, because what is worth pinning down | ||
| 19 | is what it does when the kernel takes only part of what it was offered. */ | ||
| 20 | |||
| 21 | static char *dup_of(const char *text) { | ||
| 22 | size_t len = strlen(text); | ||
| 23 | char *copy = malloc(len); | ||
| 24 | |||
| 25 | memcpy(copy, text, len); | ||
| 26 | return copy; | ||
| 27 | } | ||
| 28 | |||
| 29 | static void drain(int s, char *into, size_t len) { | ||
| 30 | size_t got = 0; | ||
| 31 | |||
| 32 | while (got < len) { | ||
| 33 | ssize_t r = read(s, into + got, len - got); | ||
| 34 | |||
| 35 | if (r <= 0) | ||
| 36 | break; | ||
| 37 | got += (size_t)r; | ||
| 38 | } | ||
| 39 | into[got] = 0; | ||
| 40 | } | ||
| 41 | |||
| 42 | static void case_queue_accounting(void) { | ||
| 43 | ot_batch batch; | ||
| 44 | |||
| 45 | memset(&batch, 0, sizeof(batch)); | ||
| 46 | |||
| 47 | test_case("a zeroed batch is empty and sends nothing"); | ||
| 48 | CHECK_INT(batch_pending(&batch), 0); | ||
| 49 | CHECK_INT(batch_send(-1, &batch), BATCH_DONE); | ||
| 50 | |||
| 51 | test_case("anything queued makes it pending"); | ||
| 52 | CHECK_INT(batch_add(&batch, dup_of("hello "), 6), 0); | ||
| 53 | CHECK_INT(batch_add_copy(&batch, "world", 5), 0); | ||
| 54 | CHECK_INT(batch_pending(&batch), 1); | ||
| 55 | CHECK_INT(batch.count, 2); | ||
| 56 | |||
| 57 | batch_reset(&batch); | ||
| 58 | test_case("and a reset empties it"); | ||
| 59 | CHECK_INT(batch_pending(&batch), 0); | ||
| 60 | CHECK_PTR(batch.entries, NULL); | ||
| 61 | } | ||
| 62 | |||
| 63 | static void case_send_in_one_go(void) { | ||
| 64 | ot_batch batch; | ||
| 65 | int sv[2]; | ||
| 66 | char got[32]; | ||
| 67 | |||
| 68 | memset(&batch, 0, sizeof(batch)); | ||
| 69 | CHECK_INT(socketpair(AF_UNIX, SOCK_STREAM, 0, sv), 0); | ||
| 70 | |||
| 71 | test_case("several buffers arrive as one stream, in order"); | ||
| 72 | CHECK_INT(batch_add(&batch, dup_of("opentracker"), 11), 0); | ||
| 73 | CHECK_INT(batch_add_copy(&batch, " says ", 6), 0); | ||
| 74 | CHECK_INT(batch_add(&batch, dup_of("hello"), 5), 0); | ||
| 75 | CHECK_INT(batch_send(sv[0], &batch), 22); | ||
| 76 | CHECK_INT(batch_pending(&batch), 0); | ||
| 77 | |||
| 78 | drain(sv[1], got, 22); | ||
| 79 | CHECK_STR(got, "opentracker says hello"); | ||
| 80 | |||
| 81 | test_case("and an empty batch reports it has nothing left"); | ||
| 82 | CHECK_INT(batch_send(sv[0], &batch), BATCH_DONE); | ||
| 83 | |||
| 84 | batch_reset(&batch); | ||
| 85 | close(sv[0]); | ||
| 86 | close(sv[1]); | ||
| 87 | } | ||
| 88 | |||
| 89 | /* Send everything the batch holds, reading the other end as we go so the | ||
| 90 | socket never stays full, and return what arrived. */ | ||
| 91 | static size_t pump(int writer, int reader, ot_batch *batch, char *into, size_t room) { | ||
| 92 | size_t got = 0; | ||
| 93 | |||
| 94 | while (batch_pending(batch) || got < room) { | ||
| 95 | ssize_t r; | ||
| 96 | |||
| 97 | if (batch_pending(batch)) { | ||
| 98 | ssize_t res = batch_send(writer, batch); | ||
| 99 | |||
| 100 | if (res == BATCH_FAILED) | ||
| 101 | break; | ||
| 102 | if (res > 0) | ||
| 103 | continue; | ||
| 104 | } | ||
| 105 | |||
| 106 | r = read(reader, into + got, room - got); | ||
| 107 | if (r <= 0) | ||
| 108 | break; | ||
| 109 | got += (size_t)r; | ||
| 110 | } | ||
| 111 | return got; | ||
| 112 | } | ||
| 113 | |||
| 114 | static void case_partial_write_resumes(void) { | ||
| 115 | const size_t size = 200000; | ||
| 116 | ot_batch batch; | ||
| 117 | int sv[2], sndbuf = 4096; | ||
| 118 | char *sent, *got; | ||
| 119 | size_t i, arrived; | ||
| 120 | |||
| 121 | memset(&batch, 0, sizeof(batch)); | ||
| 122 | CHECK_INT(socketpair(AF_UNIX, SOCK_STREAM, 0, sv), 0); | ||
| 123 | setsockopt(sv[0], SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(sndbuf)); | ||
| 124 | fcntl(sv[0], F_SETFL, fcntl(sv[0], F_GETFL, 0) | O_NONBLOCK); | ||
| 125 | |||
| 126 | sent = malloc(size); | ||
| 127 | got = malloc(size); | ||
| 128 | for (i = 0; i < size; ++i) | ||
| 129 | sent[i] = (char)(i % 251); | ||
| 130 | |||
| 131 | test_case("a buffer the kernel takes in pieces arrives whole and in order"); | ||
| 132 | CHECK_INT(batch_add(&batch, memcpy(malloc(size), sent, size), size), 0); | ||
| 133 | arrived = pump(sv[0], sv[1], &batch, got, size); | ||
| 134 | CHECK_INT(arrived, size); | ||
| 135 | CHECK_INT(batch_pending(&batch), 0); | ||
| 136 | CHECK_MEM(got, sent, size); | ||
| 137 | |||
| 138 | free(sent); | ||
| 139 | free(got); | ||
| 140 | batch_reset(&batch); | ||
| 141 | close(sv[0]); | ||
| 142 | close(sv[1]); | ||
| 143 | } | ||
| 144 | |||
| 145 | /* writev refuses more than IOV_MAX entries at once, so a long batch has to be | ||
| 146 | sent across several calls. */ | ||
| 147 | static void case_more_entries_than_one_writev(void) { | ||
| 148 | const size_t entries = 3000, each = 4; | ||
| 149 | ot_batch batch; | ||
| 150 | int sv[2], sndbuf = 4096; | ||
| 151 | char *sent, *got; | ||
| 152 | size_t i, arrived, refused = 0; | ||
| 153 | |||
| 154 | memset(&batch, 0, sizeof(batch)); | ||
| 155 | CHECK_INT(socketpair(AF_UNIX, SOCK_STREAM, 0, sv), 0); | ||
| 156 | setsockopt(sv[0], SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(sndbuf)); | ||
| 157 | fcntl(sv[0], F_SETFL, fcntl(sv[0], F_GETFL, 0) | O_NONBLOCK); | ||
| 158 | |||
| 159 | sent = malloc(entries * each); | ||
| 160 | got = malloc(entries * each); | ||
| 161 | |||
| 162 | test_case("a batch of more entries than writev accepts still arrives whole"); | ||
| 163 | for (i = 0; i < entries; ++i) { | ||
| 164 | char piece[4]; | ||
| 165 | |||
| 166 | piece[0] = (char)(i), piece[1] = (char)(i >> 8), piece[2] = 'x', piece[3] = 'y'; | ||
| 167 | memcpy(sent + i * each, piece, each); | ||
| 168 | if (batch_add(&batch, memcpy(malloc(each), piece, each), each)) | ||
| 169 | ++refused; | ||
| 170 | } | ||
| 171 | CHECK_INT(refused, 0); | ||
| 172 | CHECK_INT(batch.count, entries); | ||
| 173 | |||
| 174 | arrived = pump(sv[0], sv[1], &batch, got, entries * each); | ||
| 175 | CHECK_INT(arrived, entries * each); | ||
| 176 | CHECK_MEM(got, sent, entries * each); | ||
| 177 | |||
| 178 | free(sent); | ||
| 179 | free(got); | ||
| 180 | batch_reset(&batch); | ||
| 181 | close(sv[0]); | ||
| 182 | close(sv[1]); | ||
| 183 | } | ||
| 184 | |||
| 185 | static void case_reset_frees_the_unsent(void) { | ||
| 186 | ot_batch batch; | ||
| 187 | int sv[2]; | ||
| 188 | |||
| 189 | memset(&batch, 0, sizeof(batch)); | ||
| 190 | CHECK_INT(socketpair(AF_UNIX, SOCK_STREAM, 0, sv), 0); | ||
| 191 | |||
| 192 | test_case("a batch torn down mid flight keeps no bytes outstanding"); | ||
| 193 | CHECK_INT(batch_add(&batch, dup_of("first"), 5), 0); | ||
| 194 | CHECK_INT(batch_add(&batch, dup_of("second"), 6), 0); | ||
| 195 | CHECK_INT(batch_send(sv[0], &batch), 11); | ||
| 196 | CHECK_INT(batch_add(&batch, dup_of("never sent"), 10), 0); | ||
| 197 | CHECK_INT(batch_pending(&batch), 1); | ||
| 198 | |||
| 199 | /* Whether the unsent buffer is freed is for the sanitizer to say */ | ||
| 200 | batch_reset(&batch); | ||
| 201 | CHECK_INT(batch_pending(&batch), 0); | ||
| 202 | CHECK_INT(batch.count, 0); | ||
| 203 | |||
| 204 | close(sv[0]); | ||
| 205 | close(sv[1]); | ||
| 206 | } | ||
| 207 | |||
| 208 | static void case_closed_socket_is_fatal(void) { | ||
| 209 | ot_batch batch; | ||
| 210 | int sv[2]; | ||
| 211 | |||
| 212 | memset(&batch, 0, sizeof(batch)); | ||
| 213 | CHECK_INT(socketpair(AF_UNIX, SOCK_STREAM, 0, sv), 0); | ||
| 214 | close(sv[1]); | ||
| 215 | |||
| 216 | test_case("writing to a socket whose peer is gone reports a dead connection"); | ||
| 217 | CHECK_INT(batch_add(&batch, dup_of("nobody is listening"), 19), 0); | ||
| 218 | CHECK_INT(batch_send(sv[0], &batch), BATCH_FAILED); | ||
| 219 | |||
| 220 | batch_reset(&batch); | ||
| 221 | close(sv[0]); | ||
| 222 | } | ||
| 223 | |||
| 224 | static void case_vector_is_taken_whole(void) { | ||
| 225 | ot_batch batch; | ||
| 226 | struct iovec *vector = malloc(3 * sizeof(struct iovec)); | ||
| 227 | int sv[2]; | ||
| 228 | char got[32]; | ||
| 229 | |||
| 230 | memset(&batch, 0, sizeof(batch)); | ||
| 231 | CHECK_INT(socketpair(AF_UNIX, SOCK_STREAM, 0, sv), 0); | ||
| 232 | |||
| 233 | test_case("a whole vector is queued in one go, in order"); | ||
| 234 | vector[0].iov_base = dup_of("one "), vector[0].iov_len = 4; | ||
| 235 | vector[1].iov_base = dup_of("two "), vector[1].iov_len = 4; | ||
| 236 | vector[2].iov_base = dup_of("three"), vector[2].iov_len = 5; | ||
| 237 | CHECK_INT(batch_add_vector(&batch, vector, 3), 0); | ||
| 238 | free(vector); | ||
| 239 | CHECK_INT(batch.count, 3); | ||
| 240 | CHECK_INT(batch_send(sv[0], &batch), 13); | ||
| 241 | |||
| 242 | drain(sv[1], got, 13); | ||
| 243 | CHECK_STR(got, "one two three"); | ||
| 244 | |||
| 245 | batch_reset(&batch); | ||
| 246 | close(sv[0]); | ||
| 247 | close(sv[1]); | ||
| 248 | } | ||
| 249 | |||
| 250 | /* The entry array starts small and doubles, so crossing that boundary one | ||
| 251 | buffer at a time is worth its own case. */ | ||
| 252 | static void case_growing_one_entry_at_a_time(void) { | ||
| 253 | const size_t entries = 100; | ||
| 254 | ot_batch batch; | ||
| 255 | int sv[2]; | ||
| 256 | char sent[400], got[401]; | ||
| 257 | size_t i, refused = 0; | ||
| 258 | |||
| 259 | memset(&batch, 0, sizeof(batch)); | ||
| 260 | CHECK_INT(socketpair(AF_UNIX, SOCK_STREAM, 0, sv), 0); | ||
| 261 | |||
| 262 | test_case("a batch grown one buffer at a time keeps them all, in order"); | ||
| 263 | for (i = 0; i < entries; ++i) { | ||
| 264 | char piece[4]; | ||
| 265 | |||
| 266 | piece[0] = (char)('a' + i % 26), piece[1] = (char)('0' + i % 10); | ||
| 267 | piece[2] = (char)('A' + i % 26), piece[3] = '.'; | ||
| 268 | memcpy(sent + i * 4, piece, 4); | ||
| 269 | if (batch_add(&batch, memcpy(malloc(4), piece, 4), 4)) | ||
| 270 | ++refused; | ||
| 271 | } | ||
| 272 | CHECK_INT(refused, 0); | ||
| 273 | CHECK_INT(batch.count, entries); | ||
| 274 | CHECK_INT(batch_send(sv[0], &batch), (ssize_t)(entries * 4)); | ||
| 275 | |||
| 276 | drain(sv[1], got, entries * 4); | ||
| 277 | CHECK_MEM(got, sent, entries * 4); | ||
| 278 | |||
| 279 | batch_reset(&batch); | ||
| 280 | close(sv[0]); | ||
| 281 | close(sv[1]); | ||
| 282 | } | ||
| 283 | |||
| 284 | /* Appending while earlier entries are still on their way is what a chunked | ||
| 285 | transfer does. Rather than depend on when a kernel decides to block, this | ||
| 286 | takes entries off the front the way a completed send would and checks that | ||
| 287 | the array reuses the space instead of growing. */ | ||
| 288 | static void case_append_while_sending(void) { | ||
| 289 | const size_t per_round = 12, each = 8; | ||
| 290 | ot_batch batch; | ||
| 291 | int sv[2]; | ||
| 292 | char expected[16], got[17]; | ||
| 293 | size_t i, round, refused = 0, space_after_first = 0; | ||
| 294 | |||
| 295 | memset(&batch, 0, sizeof(batch)); | ||
| 296 | CHECK_INT(socketpair(AF_UNIX, SOCK_STREAM, 0, sv), 0); | ||
| 297 | |||
| 298 | test_case("an array whose front has gone out is reused, not grown"); | ||
| 299 | for (round = 0; round < 8; ++round) { | ||
| 300 | for (i = 0; i < per_round; ++i) { | ||
| 301 | /* Distinct per entry, so a move from the wrong index shows up */ | ||
| 302 | char *piece = malloc(each); | ||
| 303 | |||
| 304 | memset(piece, (int)('a' + (round * per_round + i) % 26), each); | ||
| 305 | piece[0] = (char)('0' + i); | ||
| 306 | piece[1] = (char)('0' + round); | ||
| 307 | if (batch_add(&batch, piece, each)) | ||
| 308 | ++refused; | ||
| 309 | } | ||
| 310 | |||
| 311 | /* All but the last entry have left and been freed */ | ||
| 312 | while (batch.count - batch.first > 1) | ||
| 313 | free(batch.entries[batch.first++].iov_base); | ||
| 314 | |||
| 315 | if (!round) | ||
| 316 | space_after_first = batch.space; | ||
| 317 | } | ||
| 318 | CHECK_INT(refused, 0); | ||
| 319 | CHECK_INT(batch.space, space_after_first); | ||
| 320 | |||
| 321 | test_case("and what is still queued survives the move, in order"); | ||
| 322 | CHECK_INT(batch.count - batch.first, 1); | ||
| 323 | CHECK_INT(batch_add(&batch, memset(malloc(each), 'Z', each), each), 0); | ||
| 324 | CHECK_INT(batch_send(sv[0], &batch), (ssize_t)(2 * each)); | ||
| 325 | |||
| 326 | memset(expected, (int)('a' + (7 * per_round + per_round - 1) % 26), each); | ||
| 327 | expected[0] = (char)('0' + per_round - 1); | ||
| 328 | expected[1] = '7'; | ||
| 329 | memset(expected + each, 'Z', each); | ||
| 330 | drain(sv[1], got, 2 * each); | ||
| 331 | CHECK_MEM(got, expected, 2 * each); | ||
| 332 | |||
| 333 | batch_reset(&batch); | ||
| 334 | close(sv[0]); | ||
| 335 | close(sv[1]); | ||
| 336 | } | ||
| 337 | |||
| 338 | void test_batch(void) { | ||
| 339 | case_queue_accounting(); | ||
| 340 | case_send_in_one_go(); | ||
| 341 | case_vector_is_taken_whole(); | ||
| 342 | case_growing_one_entry_at_a_time(); | ||
| 343 | case_append_while_sending(); | ||
| 344 | case_partial_write_resumes(); | ||
| 345 | case_more_entries_than_one_writev(); | ||
| 346 | case_reset_frees_the_unsent(); | ||
| 347 | case_closed_socket_is_fatal(); | ||
| 348 | } | ||
diff --git a/tests/test.h b/tests/test.h index 9677524..fd574c2 100644 --- a/tests/test.h +++ b/tests/test.h | |||
| @@ -87,5 +87,6 @@ void test_buf(void); | |||
| 87 | void test_ip(void); | 87 | void test_ip(void); |
| 88 | void test_socket(void); | 88 | void test_socket(void); |
| 89 | void test_mutex(void); | 89 | void test_mutex(void); |
| 90 | void test_batch(void); | ||
| 90 | 91 | ||
| 91 | #endif | 92 | #endif |
diff --git a/trackerlogic.c b/trackerlogic.c index 0fa7bfb..74fea6d 100644 --- a/trackerlogic.c +++ b/trackerlogic.c | |||
| @@ -10,9 +10,6 @@ | |||
| 10 | #include <stdlib.h> | 10 | #include <stdlib.h> |
| 11 | #include <string.h> | 11 | #include <string.h> |
| 12 | 12 | ||
| 13 | /* Libowfat */ | ||
| 14 | #include "io.h" | ||
| 15 | #include "iob.h" | ||
| 16 | 13 | ||
| 17 | /* Opentracker */ | 14 | /* Opentracker */ |
| 18 | #include "ot_accesslist.h" | 15 | #include "ot_accesslist.h" |
