xref: /openbsd-src/lib/libutil/imsg-buffer.c (revision ff0e7be1ebbcc809ea8ad2b6dafe215824da9e46)
1 /*	$OpenBSD: imsg-buffer.c,v 1.15 2023/05/23 12:41:28 claudio Exp $	*/
2 
3 /*
4  * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 
19 #include <sys/types.h>
20 #include <sys/queue.h>
21 #include <sys/socket.h>
22 #include <sys/uio.h>
23 
24 #include <limits.h>
25 #include <errno.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <unistd.h>
29 
30 #include "imsg.h"
31 
32 static int	ibuf_realloc(struct ibuf *, size_t);
33 static void	ibuf_enqueue(struct msgbuf *, struct ibuf *);
34 static void	ibuf_dequeue(struct msgbuf *, struct ibuf *);
35 
36 struct ibuf *
37 ibuf_open(size_t len)
38 {
39 	struct ibuf	*buf;
40 
41 	if (len == 0) {
42 		errno = EINVAL;
43 		return (NULL);
44 	}
45 	if ((buf = calloc(1, sizeof(struct ibuf))) == NULL)
46 		return (NULL);
47 	if ((buf->buf = calloc(len, 1)) == NULL) {
48 		free(buf);
49 		return (NULL);
50 	}
51 	buf->size = buf->max = len;
52 	buf->fd = -1;
53 
54 	return (buf);
55 }
56 
57 struct ibuf *
58 ibuf_dynamic(size_t len, size_t max)
59 {
60 	struct ibuf	*buf;
61 
62 	if (max < len) {
63 		errno = EINVAL;
64 		return (NULL);
65 	}
66 
67 	if ((buf = calloc(1, sizeof(struct ibuf))) == NULL)
68 		return (NULL);
69 	if (len > 0) {
70 		if ((buf->buf = calloc(len, 1)) == NULL) {
71 			free(buf);
72 			return (NULL);
73 		}
74 	}
75 	buf->size = len;
76 	buf->max = max;
77 	buf->fd = -1;
78 
79 	return (buf);
80 }
81 
82 static int
83 ibuf_realloc(struct ibuf *buf, size_t len)
84 {
85 	unsigned char	*b;
86 
87 	/* on static buffers max is eq size and so the following fails */
88 	if (len > SIZE_MAX - buf->wpos || buf->wpos + len > buf->max) {
89 		errno = ERANGE;
90 		return (-1);
91 	}
92 
93 	b = recallocarray(buf->buf, buf->size, buf->wpos + len, 1);
94 	if (b == NULL)
95 		return (-1);
96 	buf->buf = b;
97 	buf->size = buf->wpos + len;
98 
99 	return (0);
100 }
101 
102 int
103 ibuf_add(struct ibuf *buf, const void *data, size_t len)
104 {
105 	if (len > SIZE_MAX - buf->wpos) {
106 		errno = ERANGE;
107 		return (-1);
108 	}
109 
110 	if (buf->wpos + len > buf->size)
111 		if (ibuf_realloc(buf, len) == -1)
112 			return (-1);
113 
114 	memcpy(buf->buf + buf->wpos, data, len);
115 	buf->wpos += len;
116 	return (0);
117 }
118 
119 void *
120 ibuf_reserve(struct ibuf *buf, size_t len)
121 {
122 	void	*b;
123 
124 	if (len > SIZE_MAX - buf->wpos) {
125 		errno = ERANGE;
126 		return (NULL);
127 	}
128 
129 	if (buf->wpos + len > buf->size)
130 		if (ibuf_realloc(buf, len) == -1)
131 			return (NULL);
132 
133 	b = buf->buf + buf->wpos;
134 	buf->wpos += len;
135 	memset(b, 0, len);
136 	return (b);
137 }
138 
139 void *
140 ibuf_seek(struct ibuf *buf, size_t pos, size_t len)
141 {
142 	/* only allowed to seek in already written parts */
143 	if (len > SIZE_MAX - pos || pos + len > buf->wpos)
144 		return (NULL);
145 
146 	return (buf->buf + pos);
147 }
148 
149 size_t
150 ibuf_size(struct ibuf *buf)
151 {
152 	return (buf->wpos);
153 }
154 
155 size_t
156 ibuf_left(struct ibuf *buf)
157 {
158 	return (buf->max - buf->wpos);
159 }
160 
161 void
162 ibuf_close(struct msgbuf *msgbuf, struct ibuf *buf)
163 {
164 	ibuf_enqueue(msgbuf, buf);
165 }
166 
167 int
168 ibuf_write(struct msgbuf *msgbuf)
169 {
170 	struct iovec	 iov[IOV_MAX];
171 	struct ibuf	*buf;
172 	unsigned int	 i = 0;
173 	ssize_t	n;
174 
175 	memset(&iov, 0, sizeof(iov));
176 	TAILQ_FOREACH(buf, &msgbuf->bufs, entry) {
177 		if (i >= IOV_MAX)
178 			break;
179 		iov[i].iov_base = buf->buf + buf->rpos;
180 		iov[i].iov_len = buf->wpos - buf->rpos;
181 		i++;
182 	}
183 
184 again:
185 	if ((n = writev(msgbuf->fd, iov, i)) == -1) {
186 		if (errno == EINTR)
187 			goto again;
188 		if (errno == ENOBUFS)
189 			errno = EAGAIN;
190 		return (-1);
191 	}
192 
193 	if (n == 0) {			/* connection closed */
194 		errno = 0;
195 		return (0);
196 	}
197 
198 	msgbuf_drain(msgbuf, n);
199 
200 	return (1);
201 }
202 
203 void
204 ibuf_free(struct ibuf *buf)
205 {
206 	if (buf == NULL)
207 		return;
208 	freezero(buf->buf, buf->size);
209 	free(buf);
210 }
211 
212 void
213 msgbuf_init(struct msgbuf *msgbuf)
214 {
215 	msgbuf->queued = 0;
216 	msgbuf->fd = -1;
217 	TAILQ_INIT(&msgbuf->bufs);
218 }
219 
220 void
221 msgbuf_drain(struct msgbuf *msgbuf, size_t n)
222 {
223 	struct ibuf	*buf, *next;
224 
225 	for (buf = TAILQ_FIRST(&msgbuf->bufs); buf != NULL && n > 0;
226 	    buf = next) {
227 		next = TAILQ_NEXT(buf, entry);
228 		if (n >= buf->wpos - buf->rpos) {
229 			n -= buf->wpos - buf->rpos;
230 			ibuf_dequeue(msgbuf, buf);
231 		} else {
232 			buf->rpos += n;
233 			n = 0;
234 		}
235 	}
236 }
237 
238 void
239 msgbuf_clear(struct msgbuf *msgbuf)
240 {
241 	struct ibuf	*buf;
242 
243 	while ((buf = TAILQ_FIRST(&msgbuf->bufs)) != NULL)
244 		ibuf_dequeue(msgbuf, buf);
245 }
246 
247 int
248 msgbuf_write(struct msgbuf *msgbuf)
249 {
250 	struct iovec	 iov[IOV_MAX];
251 	struct ibuf	*buf, *buf0 = NULL;
252 	unsigned int	 i = 0;
253 	ssize_t		 n;
254 	struct msghdr	 msg;
255 	struct cmsghdr	*cmsg;
256 	union {
257 		struct cmsghdr	hdr;
258 		char		buf[CMSG_SPACE(sizeof(int))];
259 	} cmsgbuf;
260 
261 	memset(&iov, 0, sizeof(iov));
262 	memset(&msg, 0, sizeof(msg));
263 	memset(&cmsgbuf, 0, sizeof(cmsgbuf));
264 	TAILQ_FOREACH(buf, &msgbuf->bufs, entry) {
265 		if (i >= IOV_MAX)
266 			break;
267 		if (i > 0 && buf->fd != -1)
268 			break;
269 		iov[i].iov_base = buf->buf + buf->rpos;
270 		iov[i].iov_len = buf->wpos - buf->rpos;
271 		i++;
272 		if (buf->fd != -1)
273 			buf0 = buf;
274 	}
275 
276 	msg.msg_iov = iov;
277 	msg.msg_iovlen = i;
278 
279 	if (buf0 != NULL) {
280 		msg.msg_control = (caddr_t)&cmsgbuf.buf;
281 		msg.msg_controllen = sizeof(cmsgbuf.buf);
282 		cmsg = CMSG_FIRSTHDR(&msg);
283 		cmsg->cmsg_len = CMSG_LEN(sizeof(int));
284 		cmsg->cmsg_level = SOL_SOCKET;
285 		cmsg->cmsg_type = SCM_RIGHTS;
286 		*(int *)CMSG_DATA(cmsg) = buf0->fd;
287 	}
288 
289 again:
290 	if ((n = sendmsg(msgbuf->fd, &msg, 0)) == -1) {
291 		if (errno == EINTR)
292 			goto again;
293 		if (errno == ENOBUFS)
294 			errno = EAGAIN;
295 		return (-1);
296 	}
297 
298 	if (n == 0) {			/* connection closed */
299 		errno = 0;
300 		return (0);
301 	}
302 
303 	/*
304 	 * assumption: fd got sent if sendmsg sent anything
305 	 * this works because fds are passed one at a time
306 	 */
307 	if (buf0 != NULL) {
308 		close(buf0->fd);
309 		buf0->fd = -1;
310 	}
311 
312 	msgbuf_drain(msgbuf, n);
313 
314 	return (1);
315 }
316 
317 static void
318 ibuf_enqueue(struct msgbuf *msgbuf, struct ibuf *buf)
319 {
320 	TAILQ_INSERT_TAIL(&msgbuf->bufs, buf, entry);
321 	msgbuf->queued++;
322 }
323 
324 static void
325 ibuf_dequeue(struct msgbuf *msgbuf, struct ibuf *buf)
326 {
327 	TAILQ_REMOVE(&msgbuf->bufs, buf, entry);
328 
329 	if (buf->fd != -1)
330 		close(buf->fd);
331 
332 	msgbuf->queued--;
333 	ibuf_free(buf);
334 }
335