lib: Fixed potential kqueue timespec storage bug.
[ashd.git] / lib / mtio-kqueue.c
1 /*
2     ashd - A Sane HTTP Daemon
3     Copyright (C) 2008  Fredrik Tolf <fredrik@dolda2000.com>
4
5     This program is free software: you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation, either version 3 of the License, or
8     (at your option) any later version.
9
10     This program is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU General Public License for more details.
14
15     You should have received a copy of the GNU General Public License
16     along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #include <stdlib.h>
20 #include <unistd.h>
21 #include <time.h>
22 #include <fcntl.h>
23 #include <string.h>
24 #include <sys/event.h>
25 #include <errno.h>
26
27 #ifdef HAVE_CONFIG_H
28 #include <config.h>
29 #endif
30 #include <log.h>
31 #include <utils.h>
32 #include <mt.h>
33 #include <mtio.h>
34
35 static struct blocker *blockers;
36
37 struct blocker {
38     struct blocker *n, *p, *n2, *p2;
39     int fd, reg;
40     int ev, rev, id;
41     int thpos;
42     time_t to;
43     struct muth *th;
44 };
45
46 static int qfd = -1, fdln = 0;
47 static int exitstatus;
48 static struct blocker **fdlist;
49 static typedbuf(struct blocker *) timeheap;
50
51 static int regfd(struct blocker *bl)
52 {
53     struct blocker *o;
54     int prev;
55     struct kevent evd;
56     
57     if(bl->fd >= fdln) {
58         if(fdlist) {
59             fdlist = srealloc(fdlist, sizeof(*fdlist) * (bl->fd + 1));
60             memset(fdlist + fdln, 0, sizeof(*fdlist) * (bl->fd + 1 - fdln));
61             fdln = bl->fd + 1;
62         } else {
63             fdlist = szmalloc(sizeof(*fdlist) * (fdln = (bl->fd + 1)));
64         }
65     }
66     for(prev = 0, o = fdlist[bl->fd]; o; o = o->n2)
67         prev |= o->ev;
68     if((bl->ev & EV_READ) && !(prev & EV_READ)) {
69         evd = (struct kevent) {
70             .flags = EV_ADD,
71             .ident = bl->fd,
72             .filter = EVFILT_READ,
73         };
74         if(kevent(qfd, &evd, 1, NULL, 0, NULL) < 0) {
75             /* XXX?! Whatever to do, really? */
76             flog(LOG_ERR, "kevent(EV_ADD, EVFILT_READ) on fd %i: %s", bl->fd, strerror(errno));
77             return(-1);
78         }
79     }
80     if((bl->ev & EV_WRITE) && !(prev & EV_WRITE)) {
81         evd = (struct kevent) {
82             .flags = EV_ADD,
83             .ident = bl->fd,
84             .filter = EVFILT_WRITE,
85         };
86         if(kevent(qfd, &evd, 1, NULL, 0, NULL) < 0) {
87             /* XXX?! Whatever to do, really? */
88             flog(LOG_ERR, "kevent(EV_ADD, EVFILT_WRITE) on fd %i: %s", bl->fd, strerror(errno));
89             return(-1);
90         }
91     }
92     bl->n2 = fdlist[bl->fd];
93     bl->p2 = NULL;
94     if(fdlist[bl->fd] != NULL)
95         fdlist[bl->fd]->p2 = bl;
96     fdlist[bl->fd] = bl;
97     bl->reg = 1;
98     return(0);
99 }
100
101 static void remfd(struct blocker *bl)
102 {
103     struct blocker *o;
104     struct kevent evd;
105     int left;
106     
107     if(!bl->reg)
108         return;
109     if(bl->n2)
110         bl->n2->p2 = bl->p2;
111     if(bl->p2)
112         bl->p2->n2 = bl->n2;
113     if(bl == fdlist[bl->fd])
114         fdlist[bl->fd] = bl->n2;
115     for(left = 0, o = fdlist[bl->fd]; o; o = o->n2)
116         left |= o->ev;
117     if((bl->ev & EV_READ) && !(left & EV_READ)) {
118         evd = (struct kevent) {
119             .flags = EV_DELETE,
120             .ident = bl->fd,
121             .filter = EVFILT_READ,
122         };
123         if(kevent(qfd, &evd, 1, NULL, 0, NULL) < 0) {
124             /* XXX?! Whatever to do, really? */
125             flog(LOG_ERR, "kevent(EV_DELETE, EVFILT_READ) on fd %i: %s", bl->fd, strerror(errno));
126         }
127     }
128     if((bl->ev & EV_WRITE) && !(left & EV_WRITE)) {
129         evd = (struct kevent) {
130             .flags = EV_DELETE,
131             .ident = bl->fd,
132             .filter = EVFILT_WRITE,
133         };
134         if(kevent(qfd, &evd, 1, NULL, 0, NULL) < 0) {
135             /* XXX?! Whatever to do, really? */
136             flog(LOG_ERR, "kevent(EV_DELETE, EVFILT_WRITE) on fd %i: %s", bl->fd, strerror(errno));
137         }
138     }
139     bl->reg = 0;
140 }
141
142 static void thraise(struct blocker *bl, int n)
143 {
144     int p;
145     
146     while(n > 0) {
147         p = (n - 1) >> 1;
148         if(timeheap.b[p]->to <= bl->to)
149             break;
150         timeheap.b[n] = timeheap.b[p];
151         timeheap.b[n]->thpos = n;
152         n = p;
153     }
154     timeheap.b[n] = bl;
155     bl->thpos = n;
156 }
157
158 static void thlower(struct blocker *bl, int n)
159 {
160     int c;
161     
162     while(1) {
163         c = (n << 1) + 1;
164         if(c >= timeheap.d)
165             break;
166         if((c + 1 < timeheap.d) && (timeheap.b[c + 1]->to < timeheap.b[c]->to))
167             c = c + 1;
168         if(timeheap.b[c]->to > bl->to)
169             break;
170         timeheap.b[n] = timeheap.b[c];
171         timeheap.b[n]->thpos = n;
172         n = c;
173     }
174     timeheap.b[n] = bl;
175     bl->thpos = n;
176 }
177
178 static void addtimeout(struct blocker *bl, time_t to)
179 {
180     sizebuf(timeheap, ++timeheap.d);
181     thraise(bl, timeheap.d - 1);
182 }
183
184 static void deltimeout(struct blocker *bl)
185 {
186     int n;
187     
188     if(bl->thpos == timeheap.d - 1) {
189         timeheap.d--;
190         return;
191     }
192     n = bl->thpos;
193     bl = timeheap.b[--timeheap.d];
194     if((n > 0) && (timeheap.b[(n - 1) >> 1]->to > bl->to))
195         thraise(bl, n);
196     else
197         thlower(bl, n);
198 }
199
200 static int addblock(struct blocker *bl)
201 {
202     if((qfd >= 0) && regfd(bl))
203         return(-1);
204     bl->n = blockers;
205     if(blockers)
206         blockers->p = bl;
207     blockers = bl;
208     if(bl->to > 0)
209         addtimeout(bl, bl->to);
210     return(0);
211 }
212
213 static void remblock(struct blocker *bl)
214 {
215     if(bl->to > 0)
216         deltimeout(bl);
217     if(bl->n)
218         bl->n->p = bl->p;
219     if(bl->p)
220         bl->p->n = bl->n;
221     if(bl == blockers)
222         blockers = bl->n;
223     remfd(bl);
224 }
225
226 struct selected mblock(time_t to, int n, struct selected *spec)
227 {
228     int i, id;
229     struct blocker bls[n];
230     
231     to = (to > 0)?(time(NULL) + to):0;
232     for(i = 0; i < n; i++) {
233         bls[i] = (struct blocker) {
234             .fd = spec[i].fd,
235             .ev = spec[i].ev,
236             .id = i,
237             .to = to,
238             .th = current,
239         };
240         if(addblock(&bls[i])) {
241             for(i--; i >= 0; i--)
242                 remblock(&bls[i]);
243             return((struct selected){.fd = -1, .ev = -1});
244         }
245     }
246     id = yield();
247     for(i = 0; i < n; i++)
248         remblock(&bls[i]);
249     if(id < 0)
250         return((struct selected){.fd = -1, .ev = -1});
251     return((struct selected){.fd = bls[id].fd, .ev = bls[id].rev});
252 }
253
254 int block(int fd, int ev, time_t to)
255 {
256     struct blocker bl;
257     int rv;
258     
259     bl = (struct blocker) {
260         .fd = fd,
261         .ev = ev,
262         .id = -1,
263         .to = (to > 0)?(time(NULL) + to):0,
264         .th = current,
265     };
266     addblock(&bl);
267     rv = yield();
268     remblock(&bl);
269     return(rv);
270 }
271
272 int ioloop(void)
273 {
274     struct blocker *bl, *nbl;
275     struct kevent evs[16];
276     int i, fd, nev, ev;
277     time_t now;
278     struct timespec *toval;
279     
280     exitstatus = 0;
281     qfd = kqueue();
282     fcntl(qfd, F_SETFD, FD_CLOEXEC);
283     for(bl = blockers; bl; bl = nbl) {
284         nbl = bl->n;
285         if(regfd(bl))
286             resume(bl->th, -1);
287     }
288     while(blockers != NULL) {
289         now = time(NULL);
290         toval = &(struct timespec){};
291         if(timeheap.d == 0)
292             toval  = NULL;
293         else if(timeheap.b[0]->to > now)
294             *toval = (struct timespec){.tv_sec = timeheap.b[0]->to - now};
295         else
296             *toval = (struct timespec){.tv_sec = 1};
297         if(exitstatus)
298             break;
299         nev = kevent(qfd, NULL, 0, evs, sizeof(evs) / sizeof(*evs), toval);
300         if(nev < 0) {
301             if(errno != EINTR) {
302                 flog(LOG_CRIT, "ioloop: kevent errored out: %s", strerror(errno));
303                 /* To avoid CPU hogging in case it's bad, which it
304                  * probably is. */
305                 sleep(1);
306             }
307             continue;
308         }
309         for(i = 0; i < nev; i++) {
310             fd = (int)evs[i].ident;
311             ev = (evs[i].filter == EVFILT_READ)?EV_READ:EV_WRITE;
312             for(bl = fdlist[fd]; bl; bl = nbl) {
313                 nbl = bl->n2;
314                 if(ev & bl->ev) {
315                     if(bl->id < 0) {
316                         resume(bl->th, ev);
317                     } else {
318                         bl->rev = ev;
319                         resume(bl->th, bl->id);
320                     }
321                 }
322             }
323         }
324         now = time(NULL);
325         while((timeheap.d > 0) && ((bl = timeheap.b[0])->to <= now)) {
326             if(bl->id < 0) {
327                 resume(bl->th, 0);
328             } else {
329                 bl->rev = 0;
330                 resume(bl->th, bl->id);
331             }
332         }
333     }
334     for(bl = blockers; bl; bl = bl->n)
335         remfd(bl);
336     close(qfd);
337     qfd = -1;
338     return(exitstatus);
339 }
340
341 void exitioloop(int status)
342 {
343     exitstatus = status;
344 }