summaryrefslogtreecommitdiff
path: root/src/ot_batch.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/ot_batch.c')
-rw-r--r--src/ot_batch.c156
1 files changed, 156 insertions, 0 deletions
diff --git a/src/ot_batch.c b/src/ot_batch.c
new file mode 100644
index 0000000..e4dda0b
--- /dev/null
+++ b/src/ot_batch.c
@@ -0,0 +1,156 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever. */
3
4/* System */
5#include <errno.h>
6#include <limits.h>
7#include <stdlib.h>
8#include <string.h>
9#include <sys/socket.h>
10#include <sys/uio.h>
11#include <unistd.h>
12
13/* Opentracker */
14#include "ot_batch.h"
15
16#ifndef IOV_MAX
17#define IOV_MAX 1024
18#endif
19
20#define OT_BATCH_MIN_ENTRIES 16
21
22static int batch_room_for(ot_batch *batch, size_t wanted) {
23 size_t space = batch->space ? batch->space : OT_BATCH_MIN_ENTRIES;
24 struct iovec *grown;
25
26 if (batch->count + wanted <= batch->space)
27 return 0;
28
29 /* During a chunked transfer entries are appended while earlier ones are
30 still going out, so the front of the array frees up as we go. Slide the
31 rest down before asking for more. */
32 if (batch->first && batch->count - batch->first + wanted <= batch->space) {
33 memmove(batch->entries, batch->entries + batch->first, (batch->count - batch->first) * sizeof(struct iovec));
34 batch->count -= batch->first;
35 batch->first = 0;
36 return 0;
37 }
38
39 while (space < batch->count + wanted)
40 space *= 2;
41
42 if (!(grown = realloc(batch->entries, space * sizeof(struct iovec))))
43 return -1;
44
45 batch->entries = grown;
46 batch->space = space;
47 return 0;
48}
49
50int batch_add(ot_batch *batch, void *buf, size_t len) {
51 if (batch_room_for(batch, 1)) {
52 free(buf);
53 return -1;
54 }
55
56 batch->entries[batch->count].iov_base = buf;
57 batch->entries[batch->count].iov_len = len;
58 ++batch->count;
59 return 0;
60}
61
62int batch_add_vector(ot_batch *batch, const struct iovec *entries, size_t count) {
63 if (batch_room_for(batch, count)) {
64 while (count--)
65 free(entries[count].iov_base);
66 return -1;
67 }
68
69 memcpy(batch->entries + batch->count, entries, count * sizeof(struct iovec));
70 batch->count += count;
71 return 0;
72}
73
74/* Drop everything the kernel accepted, freeing each buffer as it goes */
75static void batch_consume(ot_batch *batch, size_t written) {
76 while (written) {
77 struct iovec *entry = batch->entries + batch->first;
78 size_t left = entry->iov_len - batch->offset;
79
80 if (written < left) {
81 batch->offset += written;
82 return;
83 }
84
85 free(entry->iov_base);
86 written -= left;
87 batch->offset = 0;
88 ++batch->first;
89 }
90
91 /* Nothing is outstanding, so the array can start over from the front */
92 if (batch->first == batch->count)
93 batch->first = batch->count = 0;
94}
95
96/* A peer that has gone away must come back as an error rather than as a
97 signal that kills the tracker, so ask the kernel not to raise one where it
98 can be asked. macOS cannot, and relies on SIGPIPE being blocked. */
99static ssize_t batch_writev(int s, struct iovec *entries, int count) {
100#ifdef MSG_NOSIGNAL
101 struct msghdr msg;
102
103 memset(&msg, 0, sizeof(msg));
104 msg.msg_iov = entries;
105 msg.msg_iovlen = count;
106 return sendmsg(s, &msg, MSG_NOSIGNAL);
107#else
108 return writev(s, entries, count);
109#endif
110}
111
112ssize_t batch_send(int s, ot_batch *batch) {
113 size_t entries = batch->count - batch->first;
114 ssize_t written;
115
116 if (!batch_pending(batch))
117 return BATCH_DONE;
118
119 if (entries > IOV_MAX)
120 entries = IOV_MAX;
121
122 /* The first entry may be half way out already */
123 batch->entries[batch->first].iov_base = (char *)batch->entries[batch->first].iov_base + batch->offset;
124 batch->entries[batch->first].iov_len -= batch->offset;
125 written = batch_writev(s, batch->entries + batch->first, (int)entries);
126 batch->entries[batch->first].iov_base = (char *)batch->entries[batch->first].iov_base - batch->offset;
127 batch->entries[batch->first].iov_len += batch->offset;
128
129 if (written < 0)
130 return (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) ? BATCH_AGAIN : BATCH_FAILED;
131 if (!written)
132 return BATCH_FAILED;
133
134 batch_consume(batch, (size_t)written);
135 return written;
136}
137
138void batch_reset(ot_batch *batch) {
139 while (batch->first < batch->count)
140 free(batch->entries[batch->first++].iov_base);
141
142 free(batch->entries);
143 batch->entries = NULL;
144 batch->count = batch->first = batch->offset = batch->space = 0;
145}
146
147/* For the few constants the chunked encoding needs, which the batch frees
148 along with everything else */
149int batch_add_copy(ot_batch *batch, const void *buf, size_t len) {
150 void *copy = malloc(len);
151
152 if (!copy)
153 return -1;
154 memcpy(copy, buf, len);
155 return batch_add(batch, copy, len);
156}