More explicit type for events && cleanup event_cancel.
[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         close(server->fd);
56         server->fd = -1;
57     }
58     if (server->fd2 > 0) {
59         close(server->fd2);
60         server->fd2 = -1;
61     }
62     if (server->data && server->clear_data) {
63         server->clear_data(&server->data);
64     }
65     array_shrink(server->ibuf, 512);
66     array_shrink(server->obuf, 512);
67 }
68
69 static void server_delete(server_t **server)
70 {
71     if (*server) {
72         buffer_wipe(&(*server)->ibuf);
73         buffer_wipe(&(*server)->obuf);
74         server_wipe(*server);
75         p_delete(server);
76     }
77 }
78
79 static server_t* server_acquire(void)
80 {
81     if (server_pool.len != 0) {
82         return array_elt(server_pool, --server_pool.len);
83     } else {
84         return server_new();
85     }
86 }
87
88 static void server_release(server_t *server)
89 {
90     server_wipe(server);
91     array_add(server_pool, server);
92 }
93
94 static void server_shutdown(void)
95 {
96     array_deep_wipe(listeners, server_delete);
97     array_deep_wipe(server_pool, server_delete);
98 }
99
100 module_exit(server_shutdown);
101
102 int start_server(int port, start_listener_t starter, delete_client_t deleter)
103 {
104     struct sockaddr_in addr = {
105         .sin_family = AF_INET,
106         .sin_addr   = { htonl(INADDR_LOOPBACK) },
107     };
108     server_t *tmp;
109     void* data = NULL;
110     int sock;
111
112     addr.sin_port = htons(port);
113     sock = tcp_listen_nonblock((const struct sockaddr *)&addr, sizeof(addr));
114     if (sock < 0) {
115         return -1;
116     }
117
118     if (starter) {
119       data = starter();
120       if (data == NULL) {
121         close(sock);
122         return -1;
123       }
124     }
125
126     tmp             = server_acquire();
127     tmp->fd         = sock;
128     tmp->listener   = true;
129     tmp->data       = data;
130     tmp->clear_data = deleter;
131     epoll_register(sock, EPOLLIN, tmp);
132     array_add(listeners, tmp);
133     return 0;
134 }
135
136 static int start_client(server_t *server, start_client_t starter,
137                         delete_client_t deleter)
138 {
139     server_t *tmp;
140     void* data = NULL;
141     int sock;
142
143     sock = accept_nonblock(server->fd);
144     if (sock < 0) {
145         UNIXERR("accept");
146         return -1;
147     }
148
149     if (starter) {
150         data = starter(server);
151         if (data == NULL) {
152             close(sock);
153             return -1;
154         }
155     }
156
157     tmp             = server_acquire();
158     tmp->fd         = sock;
159     tmp->data       = data;
160     tmp->clear_data = deleter;
161     epoll_register(sock, EPOLLIN, tmp);
162     return 0;
163 }
164
165 event_t event_register(void *data)
166 {
167     int fds[2];
168     if (pipe(fds) != 0) {
169         UNIXERR("pipe");
170         return INVALID_EVENT;
171     }
172     if (setnonblock(fds[0]) != 0) {
173         close(fds[0]);
174         close(fds[1]);
175         return INVALID_EVENT;
176     }
177
178     server_t *tmp = server_acquire();
179     tmp->event = true;
180     tmp->fd    = fds[0];
181     tmp->fd2   = fds[1];
182     tmp->data  = data;
183     epoll_register(fds[0], EPOLLIN, tmp);
184     return tmp->fd2;
185 }
186
187 bool event_fire(event_t event)
188 {
189     static const char *data = "";
190     return write(event, data, 1) == 0;
191 }
192
193 static bool event_cancel(int event)
194 {
195     char buff[32];
196     while (true) {
197         ssize_t res = read(event, buff, 32);
198         if (res == -1 && errno != EAGAIN && errno != EINTR) {
199             UNIXERR("read");
200             return false;
201         } else if (res == -1 && errno == EINTR) {
202             continue;
203         } else if (res != 32) {
204             return true;
205         }
206     }
207 }
208
209 int server_loop(start_client_t starter, delete_client_t deleter,
210                 run_client_t runner, event_handler_t handler,
211                 refresh_t refresh, void* config) {
212     info("entering processing loop");
213     while (!sigint) {
214         struct epoll_event evts[1024];
215         int n;
216
217         if (sighup && refresh) {
218             sighup = false;
219             info("refreshing...");
220             if (!refresh(config)) {
221                 crit("error while refreshing configuration");
222                 return EXIT_FAILURE;
223             }
224             info("refresh done, processing loop restarts");
225         }
226
227         n = epoll_select(evts, countof(evts), -1);
228         if (n < 0) {
229             if (errno != EAGAIN && errno != EINTR) {
230                 UNIXERR("epoll_wait");
231                 return EXIT_FAILURE;
232             }
233             continue;
234         }
235
236         while (--n >= 0) {
237             server_t *d = evts[n].data.ptr;
238
239             if (d->listener) {
240                 (void)start_client(d, starter, deleter);
241                 continue;
242             } else if (d->event) {
243                 if (!event_cancel(d->fd)) {
244                     server_release(d);
245                     continue;
246                 }
247                 if (handler) {
248                     if (!handler(d->data, config)) {
249                         server_release(d);
250                     }
251                 }
252                 continue;
253             }
254
255             if (evts[n].events & EPOLLIN) {
256                 if (runner(d, config) < 0) {
257                     server_release(d);
258                     continue;
259                 }
260             }
261
262             if ((evts[n].events & EPOLLOUT) && d->obuf.len) {
263                 if (buffer_write(&d->obuf, d->fd) < 0) {
264                     server_release(d);
265                     continue;
266                 }
267                 if (!d->obuf.len) {
268                     epoll_modify(d->fd, EPOLLIN, d);
269                 }
270             }
271         }
272     }
273     info("exit requested");
274     return EXIT_SUCCESS;
275 }