1 /*
2 * Empire - A multi-player, client/server Internet based war game.
3 * Copyright (C) 1986-2021, Dave Pare, Jeff Bailey, Thomas Ruschak,
4 * Ken Stevens, Steve McClure, Markus Armbruster
5 *
6 * Empire is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 *
19 * ---
20 *
21 * See files README, COPYING and CREDITS in the root of the source
22 * tree for related information and legal notices. It is expected
23 * that future projects/authors will amend these files as needed.
24 *
25 * ---
26 *
27 * ioqueue.c: Read and write i/o queues
28 *
29 * Known contributors to this file:
30 *
31 */
32
33 /*
34 * Read and write onto I/O queues. Note that
35 * the I/O queues don't actually do any writing;
36 * that is left for a higher level.
37 */
38
39 #include <config.h>
40
41 #include <stdlib.h>
42 #include <string.h>
43 #include <sys/uio.h>
44 #include "ioqueue.h"
45 #include "misc.h"
46 #include "queue.h"
47
48 struct io {
49 struct emp_qelem queue;
50 int size;
51 int nbytes;
52 int offset;
53 char *data;
54 };
55
56 struct ioqueue {
57 struct io list;
58 int bufsize;
59 int cc;
60 };
61
62 static int ioqtocbuf(struct ioqueue *ioq, char *buf, int cc, int stopc);
63 static int ioqtoiov(struct ioqueue *ioq, struct iovec *iov, int max);
64 static int ioqtobuf(struct ioqueue *ioq, char *buf, int cc);
65 static int appendcc(struct ioqueue *ioq, char *buf, int cc);
66 static int removecc(struct ioqueue *ioq, int cc);
67
68 struct ioqueue *
ioq_create(int size)69 ioq_create(int size)
70 {
71 struct ioqueue *ioq;
72
73 ioq = malloc(sizeof(struct ioqueue));
74 emp_initque(&ioq->list.queue);
75 ioq->list.nbytes = 0;
76 ioq->list.offset = 0;
77 ioq->list.size = 0;
78 ioq->list.data = NULL;
79 ioq->bufsize = size;
80 ioq->cc = 0;
81 return ioq;
82 }
83
84 void
ioq_destroy(struct ioqueue * ioq)85 ioq_destroy(struct ioqueue *ioq)
86 {
87 ioq_drain(ioq);
88 free(ioq);
89 }
90
91 void
ioq_drain(struct ioqueue * ioq)92 ioq_drain(struct ioqueue *ioq)
93 {
94 struct emp_qelem *qp;
95 struct io *io;
96
97 while ((qp = ioq->list.queue.q_forw) != &ioq->list.queue) {
98 io = (struct io *)qp;
99 emp_remque(&io->queue);
100 free(io->data);
101 free(io);
102 }
103
104 ioq->cc = 0;
105 }
106
107 /*
108 * copy batch of pointers into the passed
109 * iovec, but don't actually dequeue the data.
110 * return # of iovec initialized.
111 */
112 int
ioq_makeiov(struct ioqueue * ioq,struct iovec * iov,int cc)113 ioq_makeiov(struct ioqueue *ioq, struct iovec *iov, int cc)
114 {
115 if (ioq->cc <= 0)
116 return 0;
117 return ioqtoiov(ioq, iov, cc);
118 }
119
120 /*
121 * Copy the specified number of characters into the buffer
122 * provided, without actually dequeueing the data. Return
123 * number of bytes actually found.
124 */
125 int
ioq_peek(struct ioqueue * ioq,char * buf,int cc)126 ioq_peek(struct ioqueue *ioq, char *buf, int cc)
127 {
128 return ioqtobuf(ioq, buf, cc);
129 }
130
131 void
ioq_dequeue(struct ioqueue * ioq,int cc)132 ioq_dequeue(struct ioqueue *ioq, int cc)
133 {
134 int res = removecc(ioq, cc);
135 CANT_HAPPEN(res != cc);
136 }
137
138 void
ioq_append(struct ioqueue * ioq,char * buf,int cc)139 ioq_append(struct ioqueue *ioq, char *buf, int cc)
140 {
141 appendcc(ioq, buf, cc);
142 }
143
144 int
ioq_qsize(struct ioqueue * ioq)145 ioq_qsize(struct ioqueue *ioq)
146 {
147 return ioq->cc;
148 }
149
150 /*
151 * read a line of text up to (but not including)
152 * the newline. return -1 and read nothing if
153 * no input is available
154 */
155 int
ioq_gets(struct ioqueue * ioq,char * buf,int cc)156 ioq_gets(struct ioqueue *ioq, char *buf, int cc)
157 {
158 int nbytes;
159 int actual;
160
161 nbytes = ioqtocbuf(ioq, buf, cc - 1, '\n');
162 if (nbytes >= 0) {
163 actual = nbytes;
164 if (actual > cc - 1)
165 actual = cc - 1;
166 /* telnet terminates lines with "\r\n", get rid of \r */
167 if (actual > 0 && buf[actual-1] == '\r')
168 actual--;
169 buf[actual] = '\0';
170 /* remove the newline too */
171 removecc(ioq, nbytes + 1);
172 }
173 return nbytes;
174 }
175
176 int
ioq_puts(struct ioqueue * ioq,char * buf)177 ioq_puts(struct ioqueue *ioq, char *buf)
178 {
179 return appendcc(ioq, buf, strlen(buf));
180 }
181
182 /*
183 * copy cc bytes from ioq to buf.
184 * this routine doesn't free memory; this is
185 * left for a higher level.
186 */
187 static int
ioqtobuf(struct ioqueue * ioq,char * buf,int cc)188 ioqtobuf(struct ioqueue *ioq, char *buf, int cc)
189 {
190 struct io *io;
191 struct emp_qelem *qp;
192 struct emp_qelem *head;
193 int nbytes, nleft;
194 char *offset;
195
196 nleft = cc;
197 offset = buf;
198 head = &ioq->list.queue;
199 for (qp = head->q_forw; qp != head && nleft > 0; qp = qp->q_forw) {
200 io = (struct io *)qp;
201 if ((nbytes = io->nbytes - io->offset) < 0) {
202 /* XXX log something here */
203 continue;
204 }
205 if (nbytes > 0) {
206 if (nleft < nbytes)
207 nbytes = nleft;
208 memcpy(offset, io->data + io->offset, nbytes);
209 offset += nbytes;
210 nleft -= nbytes;
211 }
212 }
213 return offset - buf;
214 }
215
216 /*
217 * copy at most cc bytes from ioq to buf,
218 * terminating on the stop character.
219 */
220 static int
ioqtocbuf(struct ioqueue * ioq,char * buf,int cc,int stopc)221 ioqtocbuf(struct ioqueue *ioq, char *buf, int cc, int stopc)
222 {
223 int nbytes;
224 char *p;
225 int n;
226 struct io *io;
227 struct emp_qelem *qp;
228 struct emp_qelem *head;
229 int total;
230 int found;
231
232 head = &ioq->list.queue;
233 found = 0;
234 total = 0;
235 for (qp = head->q_forw; qp != head; qp = qp->q_forw) {
236 io = (struct io *)qp;
237 if ((nbytes = io->nbytes - io->offset) <= 0)
238 continue;
239 p = io->data + io->offset;
240 for (n = 0; n < nbytes && p[n] != stopc; n++) ;
241 total += n;
242 if (n < nbytes) {
243 found++;
244 break;
245 }
246 }
247 if (found == 0)
248 return -1;
249 ioqtobuf(ioq, buf, cc < total ? cc : total);
250 return total;
251 }
252
253 /*
254 * initialize an iovec to point at max bytes worth
255 * of data from the ioqueue.
256 */
257 static int
ioqtoiov(struct ioqueue * ioq,struct iovec * iov,int max)258 ioqtoiov(struct ioqueue *ioq, struct iovec *iov, int max)
259 {
260 struct io *io;
261 int cc, niov, len;
262 struct emp_qelem *qp;
263
264 cc = max;
265 niov = 0;
266 qp = ioq->list.queue.q_forw;
267 while (qp != &ioq->list.queue && cc > 0) {
268 io = (struct io *)qp;
269 len = io->nbytes - io->offset;
270 if (len > cc)
271 len = cc;
272 iov->iov_base = io->data + io->offset;
273 iov->iov_len = len;
274 cc -= len;
275 niov++;
276 iov++;
277 qp = qp->q_forw;
278 if (niov >= 16)
279 break;
280 }
281 return niov;
282 }
283
284 /*
285 * append a buffer to the end of the ioq.
286 */
287 static int
appendcc(struct ioqueue * ioq,char * buf,int cc)288 appendcc(struct ioqueue *ioq, char *buf, int cc)
289 {
290 struct io *io;
291 int len;
292 char *ptr;
293 int avail;
294
295 /* determine if any space is left */
296 io = (struct io *)ioq->list.queue.q_back;
297 avail = io->size - io->nbytes;
298 if (avail > 0) {
299 /* append to existing buffer */
300 len = cc > avail ? avail : cc;
301 memcpy(io->data + io->nbytes, buf, len);
302 io->nbytes += len;
303 ioq->cc += len;
304 if (avail < cc)
305 appendcc(ioq, buf + len, cc - len);
306 } else {
307 /* create a new buffer, minimum bufsize bytes */
308 len = cc > ioq->bufsize ? cc : ioq->bufsize;
309 ptr = malloc(len);
310 memcpy(ptr, buf, cc);
311 io = malloc(sizeof(struct io));
312 io->nbytes = cc;
313 io->size = len;
314 io->offset = 0;
315 io->data = ptr;
316 emp_insque(&io->queue, ioq->list.queue.q_back);
317 ioq->cc += cc;
318 }
319 return cc;
320 }
321
322 /*
323 * remove cc bytes from ioqueue ioq
324 * free memory, dequeue I/O elements
325 * which are no longer used.
326 */
327 static int
removecc(struct ioqueue * ioq,int cc)328 removecc(struct ioqueue *ioq, int cc)
329 {
330 struct io *io;
331 struct emp_qelem *qp;
332 int nbytes, there, remain;
333
334 nbytes = 0;
335 remain = cc;
336 while ((qp = ioq->list.queue.q_forw) != &ioq->list.queue) {
337 io = (struct io *)qp;
338 there = io->nbytes - io->offset;
339 if (there < 0) {
340 /* error */
341 emp_remque(&io->queue);
342 free(io);
343 continue;
344 }
345 if (remain >= there) {
346 /* not enough or exact; free entry */
347 nbytes += there;
348 remain -= there;
349 emp_remque(&io->queue);
350 free(io->data);
351 free(io);
352 } else {
353 /* too much; increment offset */
354 io->offset += remain;
355 nbytes += remain;
356 remain = 0;
357 }
358 if (remain <= 0)
359 break;
360 }
361 ioq->cc -= nbytes;
362 return nbytes;
363 }
364