Allow user-defined fd in events.
[apps/pfixtools.git] / common / server.c
1 /******************************************************************************/
2 /*          pfixtools: a collection of postfix related tools                  */
3 /*          ~~~~~~~~~                                                         */
4 /*  ________________________________________________________________________  */
5 /*                                                                            */
6 /*  Redistribution and use in source and binary forms, with or without        */
7 /*  modification, are permitted provided that the following conditions        */
8 /*  are met:                                                                  */
9 /*                                                                            */
10 /*  1. Redistributions of source code must retain the above copyright         */
11 /*     notice, this list of conditions and the following disclaimer.          */
12 /*  2. Redistributions in binary form must reproduce the above copyright      */
13 /*     notice, this list of conditions and the following disclaimer in the    */
14 /*     documentation and/or other materials provided with the distribution.   */
15 /*  3. The names of its contributors may not be used to endorse or promote    */
16 /*     products derived from this software without specific prior written     */
17 /*     permission.                                                            */
18 /*                                                                            */
19 /*  THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND   */
20 /*  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE     */
21 /*  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR        */
22 /*  PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS    */
23 /*  BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR    */
24 /*  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF      */
25 /*  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS  */
26 /*  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN   */
27 /*  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)   */
28 /*  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF    */
29 /*  THE POSSIBILITY OF SUCH DAMAGE.                                           */
30 /******************************************************************************/
31
32 /*
33  * Copyright © 2008 Florent Bruneau
34  */
35
36 #include "server.h"
37 #include "epoll.h"
38 #include "common.h"
39
40 static PA(server_t) listeners   = ARRAY_INIT;
41 static PA(server_t) server_pool = ARRAY_INIT;
42
43 static server_t* server_new(void)
44 {
45     server_t* server = p_new(server_t, 1);
46     server->fd  = -1;
47     server->fd2 = -1;
48     return server;
49 }
50
51 static void server_wipe(server_t *server)
52 {
53     server->listener = server->event = false;
54     if (server->fd > 0) {
55         epoll_modify(server->fd, 0, NULL);
56         close(server->fd);
57         server->fd = -1;
58     }
59     if (server->fd2 > 0) {
60         close(server->fd2);
61         server->fd2 = -1;
62     }
63     if (server->data && server->clear_data) {
64         server->clear_data(&server->data);
65     }
66 }
67
68 static void server_delete(server_t **server)
69 {
70     if (*server) {
71         buffer_wipe(&(*server)->ibuf);
72         buffer_wipe(&(*server)->obuf);
73         server_wipe(*server);
74         p_delete(server);
75     }
76 }
77
78 static server_t* server_acquire(void)
79 {
80     if (server_pool.len != 0) {
81         return array_elt(server_pool, --server_pool.len);
82     } else {
83         return server_new();
84     }
85 }
86
87 static void server_release(server_t *server)
88 {
89     server_wipe(server);
90     array_add(server_pool, server);
91 }
92
93 static void server_shutdown(void)
94 {
95     array_deep_wipe(listeners, server_delete);
96     array_deep_wipe(server_pool, server_delete);
97 }
98
99 module_exit(server_shutdown);
100
101 int start_server(int port, start_listener_t starter, delete_client_t deleter)
102 {
103     struct sockaddr_in addr = {
104         .sin_family = AF_INET,
105         .sin_addr   = { htonl(INADDR_LOOPBACK) },
106     };
107     server_t *tmp;
108     void* data = NULL;
109     int sock;
110
111     addr.sin_port = htons(port);
112     sock = tcp_listen_nonblock((const struct sockaddr *)&addr, sizeof(addr));
113     if (sock < 0) {
114         return -1;
115     }
116
117     if (starter) {
118       data = starter();
119       if (data == NULL) {
120         close(sock);
121         return -1;
122       }
123     }
124
125     tmp             = server_acquire();
126     tmp->fd         = sock;
127     tmp->listener   = true;
128     tmp->data       = data;
129     tmp->clear_data = deleter;
130     epoll_register(sock, EPOLLIN, tmp);
131     array_add(listeners, tmp);
132     return 0;
133 }
134
135 static int start_client(server_t *server, start_client_t starter,
136                         delete_client_t deleter)
137 {
138     server_t *tmp;
139     void* data = NULL;
140     int sock;
141
142     sock = accept_nonblock(server->fd);
143     if (sock < 0) {
144         UNIXERR("accept");
145         return -1;
146     }
147
148     if (starter) {
149         data = starter(server);
150         if (data == NULL) {
151             close(sock);
152             return -1;
153         }
154     }
155
156     tmp             = server_acquire();
157     tmp->fd         = sock;
158     tmp->data       = data;
159     tmp->clear_data = deleter;
160     epoll_register(sock, EPOLLIN, tmp);
161     return 0;
162 }
163
164 event_t event_register(int fd, void *data)
165 {
166     int fds[2];
167     if (fd == -1) {
168         if (pipe(fds) != 0) {
169             UNIXERR("pipe");
170             return NULL;
171         }
172         if (setnonblock(fds[0]) != 0) {
173             close(fds[0]);
174             close(fds[1]);
175             return NULL;
176         }
177     }
178
179     server_t *tmp = server_acquire();
180     tmp->event = true;
181     tmp->fd    = fd == -1 ? fds[0] : fd;
182     tmp->fd2   = fd == -1 ? fds[1] : -1;
183     tmp->data  = data;
184     epoll_register(fds[0], EPOLLIN, tmp);
185     return tmp;
186 }
187
188 bool event_fire(event_t event)
189 {
190     static const char *data = "";
191     if (event->fd2 == -1) {
192         return false;
193     }
194     return write(event->fd2, data, 1) == 0;
195 }
196
197 static bool event_cancel(event_t event)
198 {
199     char buff[32];
200     while (true) {
201         ssize_t res = read(event->fd, buff, 32);
202         if (res == -1 && errno != EAGAIN && errno != EINTR) {
203             UNIXERR("read");
204             return false;
205         } else if (res == -1 && errno == EINTR) {
206             continue;
207         } else if (res != 32) {
208             return true;
209         }
210     }
211 }
212
213 int server_loop(start_client_t starter, delete_client_t deleter,
214                 run_client_t runner, event_handler_t handler,
215                 refresh_t refresh, void* config)
216 {
217     info("entering processing loop");
218     while (!sigint) {
219         struct epoll_event evts[1024];
220         int n;
221
222         if (sighup && refresh) {
223             sighup = false;
224             info("refreshing...");
225             if (!refresh(config)) {
226                 crit("error while refreshing configuration");
227                 return EXIT_FAILURE;
228             }
229             info("refresh done, processing loop restarts");
230         }
231
232         n = epoll_select(evts, countof(evts), -1);
233         if (n < 0) {
234             if (errno != EAGAIN && errno != EINTR) {
235                 UNIXERR("epoll_wait");
236                 return EXIT_FAILURE;
237             }
238             continue;
239         }
240
241         while (--n >= 0) {
242             server_t *d = evts[n].data.ptr;
243
244             if (d->listener) {
245                 (void)start_client(d, starter, deleter);
246                 continue;
247             } else if (d->event) {
248                 if (!event_cancel(d)) {
249                     server_release(d);
250                     continue;
251                 }
252                 if (handler) {
253                     if (!handler(d, config)) {
254                         server_release(d);
255                     }
256                 }
257                 continue;
258             }
259
260             if (evts[n].events & EPOLLIN) {
261                 if (runner(d, config) < 0) {
262                     server_release(d);
263                     continue;
264                 }
265             }
266
267             if ((evts[n].events & EPOLLOUT) && d->obuf.len) {
268                 if (buffer_write(&d->obuf, d->fd) < 0) {
269                     server_release(d);
270                     continue;
271                 }
272                 if (!d->obuf.len) {
273                     epoll_modify(d->fd, EPOLLIN, d);
274                 }
275             }
276         }
277     }
278     info("exit requested");
279     return EXIT_SUCCESS;
280 }