xref: /openbsd/usr.sbin/ldapd/imsgev.c (revision 0d43ad28)
1 /*
2  * Copyright (c) 2009 Eric Faurot <eric@openbsd.org>
3  *
4  * Permission to use, copy, modify, and distribute this software for any
5  * purpose with or without fee is hereby granted, provided that the above
6  * copyright notice and this permission notice appear in all copies.
7  *
8  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15  */
16 
17 #include <sys/param.h>
18 #include <sys/queue.h>
19 #include <sys/socket.h>
20 #include <sys/uio.h>
21 
22 #include <errno.h>
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <unistd.h>
27 
28 #include "imsgev.h"
29 
30 void imsgev_add(struct imsgev *);
31 void imsgev_dispatch(int, short, void *);
32 void imsgev_disconnect(struct imsgev *, int);
33 
34 void
35 imsgev_init(struct imsgev *iev, int fd, void *data,
36     void (*callback)(struct imsgev *, int, struct imsg *),
37     void (*needfd)(struct imsgev *))
38 {
39 	imsg_init(&iev->ibuf, fd);
40 	iev->terminate = 0;
41 
42 	iev->data = data;
43 	iev->handler = imsgev_dispatch;
44 	iev->callback = callback;
45 	iev->needfd = needfd;
46 
47 	iev->events = EV_READ;
48 	event_set(&iev->ev, iev->ibuf.fd, iev->events, iev->handler, iev);
49 	event_add(&iev->ev, NULL);
50 }
51 
52 int
53 imsgev_compose(struct imsgev *iev, u_int16_t type, u_int32_t peerid,
54     uint32_t pid, int fd, void *data, u_int16_t datalen)
55 {
56 	int	r;
57 
58 	r = imsg_compose(&iev->ibuf, type, peerid, pid, fd, data, datalen);
59 	if (r != -1)
60 		imsgev_add(iev);
61 
62 	return (r);
63 }
64 
65 void
66 imsgev_close(struct imsgev *iev)
67 {
68 	iev->terminate = 1;
69 	imsgev_add(iev);
70 }
71 
72 void
73 imsgev_clear(struct imsgev *iev)
74 {
75 	event_del(&iev->ev);
76 	msgbuf_clear(&iev->ibuf.w);
77 	close(iev->ibuf.fd);
78 }
79 
80 void
81 imsgev_add(struct imsgev *iev)
82 {
83 	short	events = 0;
84 
85 	if (!iev->terminate)
86 		events = EV_READ;
87 	if (iev->ibuf.w.queued || iev->terminate)
88 		events |= EV_WRITE;
89 
90 	/* optimization: skip event_{del/set/add} if already set */
91 	if (events == iev->events)
92 		return;
93 
94 	iev->events = events;
95 	event_del(&iev->ev);
96 	event_set(&iev->ev, iev->ibuf.fd, iev->events, iev->handler, iev);
97 	event_add(&iev->ev, NULL);
98 }
99 
100 void
101 imsgev_dispatch(int fd, short ev, void *humppa)
102 {
103 	struct imsgev	*iev = humppa;
104 	struct imsgbuf	*ibuf = &iev->ibuf;
105 	struct imsg	 imsg;
106 	ssize_t		 n;
107 
108 	iev->events = 0;
109 
110 	if (ev & EV_READ) {
111 		if ((n = imsg_read(ibuf)) == -1) {
112 			/* if we don't have enough fds, free one up and retry */
113 			if (errno == EAGAIN) {
114 				iev->needfd(iev);
115 				n = imsg_read(ibuf);
116 			}
117 
118 			if (n == -1) {
119 				imsgev_disconnect(iev, IMSGEV_EREAD);
120 				return;
121 			}
122 		}
123 		if (n == 0) {
124 			/*
125 			 * Connection is closed for reading, and we assume
126 			 * it is also closed for writing, so we error out
127 			 * if write data is pending.
128 			 */
129 			imsgev_disconnect(iev,
130 			    (iev->ibuf.w.queued) ? IMSGEV_EWRITE : IMSGEV_DONE);
131 			return;
132 		}
133 	}
134 
135 	if (ev & EV_WRITE) {
136 		/*
137 		 * We wanted to write data out but the connection is either
138 		 * closed, or some error occured. Both case are not recoverable
139 		 * from the imsg perspective, so we treat it as a WRITE error.
140 		 */
141 		if ((n = msgbuf_write(&ibuf->w)) != 1) {
142 			imsgev_disconnect(iev, IMSGEV_EWRITE);
143 			return;
144 		}
145 	}
146 
147 	while (iev->terminate == 0) {
148 		if ((n = imsg_get(ibuf, &imsg)) == -1) {
149 			imsgev_disconnect(iev, IMSGEV_EIMSG);
150 			return;
151 		}
152 		if (n == 0)
153 			break;
154 		iev->callback(iev, IMSGEV_IMSG, &imsg);
155 		imsg_free(&imsg);
156 	}
157 
158 	if (iev->terminate && iev->ibuf.w.queued == 0) {
159 		imsgev_disconnect(iev, IMSGEV_DONE);
160 		return;
161 	}
162 
163 	imsgev_add(iev);
164 }
165 
166 void
167 imsgev_disconnect(struct imsgev *iev, int code)
168 {
169 	iev->callback(iev, code, NULL);
170 }
171