e317153ffcaf3acaf0bba6ca2cb53ce75b3cfdd2
[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     time_t to;
42     struct muth *th;
43 };
44
45 static int qfd = -1, fdln = 0;
46 static int exitstatus;
47 static struct blocker **fdlist;
48
49 static int regfd(struct blocker *bl)
50 {
51     struct blocker *o;
52     int prev;
53     struct kevent evd;
54     
55     if(bl->fd >= fdln) {
56         if(fdlist) {
57             fdlist = srealloc(fdlist, sizeof(*fdlist) * (bl->fd + 1));
58             memset(fdlist + fdln, 0, sizeof(*fdlist) * (bl->fd + 1 - fdln));
59             fdln = bl->fd + 1;
60         } else {
61             fdlist = szmalloc(sizeof(*fdlist) * (fdln = (bl->fd + 1)));
62         }
63     }
64     for(prev = 0, o = fdlist[bl->fd]; o; o = o->n2)
65         prev |= o->ev;
66     if((bl->ev & EV_READ) && !(prev & EV_READ)) {
67         evd = (struct kevent) {
68             .flags = EV_ADD,
69             .ident = bl->fd,
70             .filter = EVFILT_READ,
71         };
72         if(kevent(qfd, &evd, 1, NULL, 0, NULL) < 0) {
73             /* XXX?! Whatever to do, really? */
74             flog(LOG_ERR, "kevent(EV_ADD, EVFILT_READ) on fd %i: %s", bl->fd, strerror(errno));
75             return(-1);
76         }
77     }
78     if((bl->ev & EV_WRITE) && !(prev & EV_WRITE)) {
79         evd = (struct kevent) {
80             .flags = EV_ADD,
81             .ident = bl->fd,
82             .filter = EVFILT_WRITE,
83         };
84         if(kevent(qfd, &evd, 1, NULL, 0, NULL) < 0) {
85             /* XXX?! Whatever to do, really? */
86             flog(LOG_ERR, "kevent(EV_ADD, EVFILT_WRITE) on fd %i: %s", bl->fd, strerror(errno));
87             return(-1);
88         }
89     }
90     bl->n2 = fdlist[bl->fd];
91     bl->p2 = NULL;
92     if(fdlist[bl->fd] != NULL)
93         fdlist[bl->fd]->p2 = bl;
94     fdlist[bl->fd] = bl;
95     bl->reg = 1;
96     return(0);
97 }
98
99 static void remfd(struct blocker *bl)
100 {
101     struct blocker *o;
102     struct kevent evd;
103     int left;
104     
105     if(!bl->reg)
106         return;
107     if(bl->n2)
108         bl->n2->p2 = bl->p2;
109     if(bl->p2)
110         bl->p2->n2 = bl->n2;
111     if(bl == fdlist[bl->fd])
112         fdlist[bl->fd] = bl->n2;
113     for(left = 0, o = fdlist[bl->fd]; o; o = o->n2)
114         left |= o->ev;
115     if((bl->ev & EV_READ) && !(left & EV_READ)) {
116         evd = (struct kevent) {
117             .flags = EV_DELETE,
118             .ident = bl->fd,
119             .filter = EVFILT_READ,
120         };
121         if(kevent(qfd, &evd, 1, NULL, 0, NULL) < 0) {
122             /* XXX?! Whatever to do, really? */
123             flog(LOG_ERR, "kevent(EV_DELETE, EVFILT_READ) on fd %i: %s", bl->fd, strerror(errno));
124         }
125     }
126     if((bl->ev & EV_WRITE) && !(left & EV_WRITE)) {
127         evd = (struct kevent) {
128             .flags = EV_DELETE,
129             .ident = bl->fd,
130             .filter = EVFILT_WRITE,
131         };
132         if(kevent(qfd, &evd, 1, NULL, 0, NULL) < 0) {
133             /* XXX?! Whatever to do, really? */
134             flog(LOG_ERR, "kevent(EV_DELETE, EVFILT_WRITE) on fd %i: %s", bl->fd, strerror(errno));
135         }
136     }
137     bl->reg = 0;
138 }
139
140 static int addblock(struct blocker *bl)
141 {
142     if((qfd >= 0) && regfd(bl))
143         return(-1);
144     bl->n = blockers;
145     if(blockers)
146         blockers->p = bl;
147     blockers = bl;
148     return(0);
149 }
150
151 static void remblock(struct blocker *bl)
152 {
153     if(bl->n)
154         bl->n->p = bl->p;
155     if(bl->p)
156         bl->p->n = bl->n;
157     if(bl == blockers)
158         blockers = bl->n;
159     remfd(bl);
160 }
161
162 struct selected mblock(time_t to, int n, struct selected *spec)
163 {
164     int i, id;
165     struct blocker bls[n];
166     
167     to = (to > 0)?(time(NULL) + to):0;
168     for(i = 0; i < n; i++) {
169         bls[i] = (struct blocker) {
170             .fd = spec[i].fd,
171             .ev = spec[i].ev,
172             .id = i,
173             .to = to,
174             .th = current,
175         };
176         if(addblock(&bls[i])) {
177             for(i--; i >= 0; i--)
178                 remblock(&bls[i]);
179             return((struct selected){.fd = -1, .ev = -1});
180         }
181     }
182     id = yield();
183     for(i = 0; i < n; i++)
184         remblock(&bls[i]);
185     if(id < 0)
186         return((struct selected){.fd = -1, .ev = -1});
187     return((struct selected){.fd = bls[id].fd, .ev = bls[id].rev});
188 }
189
190 int block(int fd, int ev, time_t to)
191 {
192     struct blocker bl;
193     int rv;
194     
195     bl = (struct blocker) {
196         .fd = fd,
197         .ev = ev,
198         .id = -1,
199         .to = (to > 0)?(time(NULL) + to):0,
200         .th = current,
201     };
202     addblock(&bl);
203     rv = yield();
204     remblock(&bl);
205     return(rv);
206 }
207
208 int ioloop(void)
209 {
210     struct blocker *bl, *nbl;
211     struct kevent evs[16];
212     int i, fd, nev, ev;
213     time_t now, timeout;
214     struct timespec *toval;
215     
216     exitstatus = 0;
217     qfd = kqueue();
218     fcntl(qfd, F_SETFD, FD_CLOEXEC);
219     for(bl = blockers; bl; bl = nbl) {
220         nbl = bl->n;
221         if(regfd(bl))
222             resume(bl->th, -1);
223     }
224     while(blockers != NULL) {
225         timeout = 0;
226         for(bl = blockers; bl; bl = bl->n) {
227             if((bl->to != 0) && ((timeout == 0) || (timeout > bl->to)))
228                 timeout = bl->to;
229         }
230         now = time(NULL);
231         if(timeout == 0)
232             toval  = NULL;
233         else if(timeout > now)
234             toval = &(struct timespec){.tv_sec = timeout - now};
235         else
236             toval = &(struct timespec){.tv_sec = 1};
237         if(exitstatus)
238             break;
239         nev = kevent(qfd, NULL, 0, evs, sizeof(evs) / sizeof(*evs), toval);
240         if(nev < 0) {
241             if(errno != EINTR) {
242                 flog(LOG_CRIT, "ioloop: kevent errored out: %s", strerror(errno));
243                 /* To avoid CPU hogging in case it's bad, which it
244                  * probably is. */
245                 sleep(1);
246             }
247             continue;
248         }
249         for(i = 0; i < nev; i++) {
250             fd = (int)evs[i].ident;
251             ev = (evs[i].filter == EVFILT_READ)?EV_READ:EV_WRITE;
252             for(bl = fdlist[fd]; bl; bl = nbl) {
253                 nbl = bl->n2;
254                 if(ev & bl->ev) {
255                     if(bl->id < 0) {
256                         resume(bl->th, ev);
257                     } else {
258                         bl->rev = ev;
259                         resume(bl->th, bl->id);
260                     }
261                 }
262             }
263         }
264         now = time(NULL);
265         for(bl = blockers; bl; bl = nbl) {
266             nbl = bl->n;
267             if((bl->to != 0) && (bl->to <= now)) {
268                 if(bl->id < 0) {
269                     resume(bl->th, 0);
270                 } else {
271                     bl->rev = 0;
272                     resume(bl->th, bl->id);
273                 }
274             }
275         }
276     }
277     for(bl = blockers; bl; bl = bl->n)
278         remfd(bl);
279     close(qfd);
280     qfd = -1;
281     return(exitstatus);
282 }
283
284 void exitioloop(int status)
285 {
286     exitstatus = status;
287 }