Improve logging.
[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 <ev.h>
37 #include "server.h"
38 #include "common.h"
39
40 typedef struct server_io_t {
41     struct ev_io io;
42     int fd;
43 } server_io_t;
44
45 struct listener_t {
46     server_io_t io;
47 };
48
49 struct client_t {
50     server_io_t io;
51
52     buffer_t ibuf;
53     buffer_t obuf;
54
55     run_client_t run;
56     delete_client_t clear_data;
57     void* data;
58 };
59
60
61 static PA(listener_t) listeners = ARRAY_INIT;
62 static PA(client_t) client_pool = ARRAY_INIT;
63
64 static struct ev_loop *gl_loop           = NULL;
65 static start_client_t  gl_client_start   = NULL;
66 static delete_client_t gl_client_delete  = NULL;
67 static run_client_t    gl_client_run     = NULL;
68 static refresh_t       gl_config_refresh = NULL;
69 static void           *gl_config         = NULL;
70
71
72 /* Server io structure methods.
73  */
74
75 static inline void server_io_wipe(server_io_t *io)
76 {
77     if (io->fd >= 0) {
78         ev_io_stop(gl_loop, &io->io);
79         close(io->fd);
80         io->fd = -1;
81     }
82 }
83
84
85 /* Client methods.
86  */
87
88 /* 1 - managing clients */
89
90 static client_t* client_new(void)
91 {
92     client_t* server = p_new(client_t, 1);
93     server->io.fd  = -1;
94     return server;
95 }
96
97 static void client_wipe(client_t *server)
98 {
99     server_io_wipe(&server->io);
100     if (server->data && server->clear_data) {
101         server->clear_data(&server->data);
102     }
103     server->data = NULL;
104     server->clear_data = NULL;
105     server->run = NULL;
106 }
107
108 void client_delete(client_t **server)
109 {
110     if (*server) {
111         buffer_wipe(&(*server)->ibuf);
112         buffer_wipe(&(*server)->obuf);
113         client_wipe(*server);
114         p_delete(server);
115     }
116 }
117
118 static client_t* client_acquire(void)
119 {
120     if (client_pool.len != 0) {
121         return array_pop_last(client_pool);
122     } else {
123         return client_new();
124     }
125 }
126
127 void client_release(client_t *server)
128 {
129     client_wipe(server);
130     array_add(client_pool, server);
131 }
132
133 /* 2 - Doing I/O */
134
135 void client_io_none(client_t *server)
136 {
137     ev_io_stop(gl_loop, &server->io.io);
138 }
139
140 void client_io_rw(client_t *server)
141 {
142     ev_io_stop(gl_loop, &server->io.io);
143     ev_io_set(&server->io.io, server->io.fd, EV_READ | EV_WRITE);
144     ev_io_start(gl_loop, &server->io.io);
145 }
146
147 void client_io_ro(client_t *server)
148 {
149     ev_io_stop(gl_loop, &server->io.io);
150     ev_io_set(&server->io.io, server->io.fd, EV_READ);
151     ev_io_start(gl_loop, &server->io.io);
152 }
153
154 ssize_t client_read(client_t *client)
155 {
156     return buffer_read(&client->ibuf, client->io.fd, -1);
157 }
158
159 buffer_t *client_input_buffer(client_t *client)
160 {
161     return &client->ibuf;
162 }
163
164 buffer_t *client_output_buffer(client_t *client)
165 {
166     return &client->obuf;
167 }
168
169 void *client_data(client_t *client)
170 {
171     return client->data;
172 }
173
174
175 static void client_cb(EV_P_ struct ev_io *w, int events)
176 {
177     client_t *server = (client_t*)w;
178
179     if (events & EV_WRITE && server->obuf.len) {
180         if (buffer_write(&server->obuf, server->io.fd) < 0) {
181             client_release(server);
182             return;
183         }
184         if (!server->obuf.len) {
185             client_io_ro(server);
186         }
187     }
188
189     if (events & EV_READ) {
190         if (server->run(server, gl_config) < 0) {
191             client_release(server);
192             return;
193         }
194     }
195 }
196
197 client_t *client_register(int fd, run_client_t runner, void *data)
198 {
199     if (fd < 0) {
200         return NULL;
201     }
202
203     client_t *tmp   = client_acquire();
204     tmp->io.fd      = fd;
205     tmp->data       = data;
206     tmp->run        = runner;
207     tmp->clear_data = NULL;
208     ev_io_init(&tmp->io.io, client_cb, tmp->io.fd, EV_READ);
209     ev_io_start(gl_loop, &tmp->io.io);
210     return tmp;
211 }
212
213
214 /* Listeners management.
215  */
216
217 /* 1 - Allocation */
218
219 static listener_t *listener_new(void)
220 {
221     listener_t *io = p_new(listener_t, 1);
222     io->io.fd = -1;
223     return io;
224 }
225
226 static inline void listener_wipe(listener_t *io)
227 {
228     server_io_wipe(&io->io);
229 }
230
231 static inline void listener_delete(listener_t **io)
232 {
233     if (*io) {
234         listener_wipe(*io);
235         p_delete(io);
236     }
237 }
238
239
240 /* 2 - Management */
241
242 static void listener_cb(EV_P_ struct ev_io *w, int events)
243 {
244     listener_t *server = (listener_t*)w;
245     client_t *tmp;
246     void* data = NULL;
247     int sock;
248
249     sock = accept_nonblock(server->io.fd);
250     if (sock < 0) {
251         UNIXERR("accept");
252         ev_unloop(EV_A_ EVUNLOOP_ALL);
253         return;
254     }
255
256     if (gl_client_start) {
257         data = gl_client_start(server);
258         if (data == NULL) {
259             close(sock);
260             ev_unloop(EV_A_ EVUNLOOP_ALL);
261             return;
262         }
263     }
264
265     tmp             = client_acquire();
266     tmp->io.fd      = sock;
267     tmp->data       = data;
268     tmp->run        = gl_client_run;
269     tmp->clear_data = gl_client_delete;
270     ev_io_init(&tmp->io.io, client_cb, tmp->io.fd, EV_READ);
271     ev_io_start(gl_loop, &tmp->io.io);
272 }
273
274 listener_t *start_listener(int port)
275 {
276     struct sockaddr_in addr = {
277         .sin_family = AF_INET,
278         .sin_addr   = { htonl(INADDR_LOOPBACK) },
279     };
280     listener_t *tmp;
281     int sock;
282
283     addr.sin_port = htons(port);
284     sock = tcp_listen_nonblock((const struct sockaddr *)&addr, sizeof(addr));
285     if (sock < 0) {
286         return NULL;
287     }
288
289     tmp             = listener_new();
290     tmp->io.fd      = sock;
291     ev_io_init(&tmp->io.io, listener_cb, tmp->io.fd, EV_READ);
292     ev_io_start(gl_loop, &tmp->io.io);
293     array_add(listeners, tmp);
294     return tmp;
295 }
296
297
298
299
300 /* Server runtime stuff.
301  */
302
303 static int server_init(void)
304 {
305     gl_loop = ev_default_loop(0);
306     return 0;
307 }
308
309 static void server_shutdown(void)
310 {
311     array_deep_wipe(listeners, listener_delete);
312     array_deep_wipe(client_pool, client_delete);
313 }
314 module_init(server_init);
315 module_exit(server_shutdown);
316
317
318 static void refresh_cb(EV_P_ struct ev_signal *w, int event)
319 {
320     log_state = "refreshing ";
321     if (!gl_config_refresh(gl_config)) {
322         ev_unloop(EV_A_ EVUNLOOP_ALL);
323         info("failed");
324     } else {
325         info("done");
326     }
327     log_state = "";
328 }
329
330 static void exit_cb(EV_P_ struct ev_signal *w, int event)
331 {
332     ev_unloop(EV_A_ EVUNLOOP_ALL);
333 }
334
335 int server_loop(start_client_t starter, delete_client_t deleter,
336                 run_client_t runner, refresh_t refresh, void* config)
337 {
338     struct ev_signal ev_sighup;
339     struct ev_signal ev_sigint;
340     struct ev_signal ev_sigterm;
341
342     gl_client_start   = starter;
343     gl_client_delete  = deleter;
344     gl_client_run     = runner;
345     gl_config_refresh = refresh;
346     gl_config         = config;
347
348     if (refresh != NULL) {
349         ev_signal_init(&ev_sighup, refresh_cb, SIGHUP);
350         ev_signal_start(gl_loop, &ev_sighup);
351     }
352     ev_signal_init(&ev_sigint, exit_cb, SIGINT);
353     ev_signal_start(gl_loop, &ev_sigint);
354     ev_signal_init(&ev_sigterm, exit_cb, SIGTERM);
355     ev_signal_start(gl_loop, &ev_sigterm);
356
357     log_state = "";
358     info("entering processing loop");
359     ev_loop(gl_loop, 0);
360     info("exit requested");
361     return EXIT_SUCCESS;
362 }