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