Switch to libev.
[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 struct ev_loop *global_loop    = NULL;
44 static start_client_t  client_start   = NULL;
45 static delete_client_t client_delete  = NULL;
46 static run_client_t    client_run     = NULL;
47 static refresh_t       config_refresh = NULL;
48 static void           *config_ptr     = NULL;
49
50 static server_t* server_new(void)
51 {
52     server_t* server = p_new(server_t, 1);
53     server->fd  = -1;
54     return server;
55 }
56
57 static void server_wipe(server_t *server)
58 {
59     if (server->fd >= 0) {
60         ev_io_stop(global_loop, &server->io);
61         close(server->fd);
62         server->fd = -1;
63     }
64     if (server->data && server->clear_data) {
65         server->clear_data(&server->data);
66     }
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 void server_release(server_t *server)
89 {
90     server_wipe(server);
91     array_add(server_pool, server);
92 }
93
94 static int server_init(void)
95 {
96     global_loop = ev_default_loop(0);
97     return 0;
98 }
99
100 static void server_shutdown(void)
101 {
102     array_deep_wipe(listeners, server_delete);
103     array_deep_wipe(server_pool, server_delete);
104 }
105 module_init(server_init);
106 module_exit(server_shutdown);
107
108 static void client_cb(EV_P_ struct ev_io *w, int events)
109 {
110     server_t *server = (server_t*)w;
111
112     debug("Entering client_cb for %p, %d (%d | %d)", w, events, EV_WRITE, EV_READ);
113
114     if (events & EV_WRITE && server->obuf.len) {
115         if (buffer_write(&server->obuf, server->fd) < 0) {
116             server_release(server);
117             return;
118         }
119         if (!server->obuf.len) {
120             ev_io_set(&server->io, server->fd, EV_READ);
121         }
122     }
123
124     if (events & EV_READ) {
125         if (server->run(server, config_ptr) < 0) {
126             server_release(server);
127             return;
128         }
129     }
130 }
131
132 static int start_client(server_t *server, start_client_t starter,
133                         run_client_t runner, delete_client_t deleter)
134 {
135     server_t *tmp;
136     void* data = NULL;
137     int sock;
138
139     sock = accept_nonblock(server->fd);
140     if (sock < 0) {
141         UNIXERR("accept");
142         return -1;
143     }
144
145     if (starter) {
146         data = starter(server);
147         if (data == NULL) {
148             close(sock);
149             return -1;
150         }
151     }
152
153     tmp             = server_acquire();
154     tmp->fd         = sock;
155     tmp->data       = data;
156     tmp->run        = runner;
157     tmp->clear_data = deleter;
158     ev_io_init(&tmp->io, client_cb, tmp->fd, EV_READ);
159     ev_io_start(global_loop, &tmp->io);
160     return 0;
161 }
162
163 static void server_cb(EV_P_ struct ev_io *w, int events)
164 {
165     server_t *server = (server_t*)w;
166     if (start_client(server, client_start, client_run, client_delete) != 0) {
167         ev_unloop(EV_A_ EVUNLOOP_ALL);
168     }
169 }
170
171 int start_server(int port, start_listener_t starter, delete_client_t deleter)
172 {
173     struct sockaddr_in addr = {
174         .sin_family = AF_INET,
175         .sin_addr   = { htonl(INADDR_LOOPBACK) },
176     };
177     server_t *tmp;
178     void* data = NULL;
179     int sock;
180
181     addr.sin_port = htons(port);
182     sock = tcp_listen_nonblock((const struct sockaddr *)&addr, sizeof(addr));
183     if (sock < 0) {
184         return -1;
185     }
186
187     if (starter) {
188       data = starter();
189       if (data == NULL) {
190         close(sock);
191         return -1;
192       }
193     }
194
195     tmp             = server_acquire();
196     tmp->fd         = sock;
197     tmp->data       = data;
198     tmp->run        = NULL;
199     tmp->clear_data = deleter;
200     ev_io_init(&tmp->io, server_cb, tmp->fd, EV_READ);
201     ev_io_start(global_loop, &tmp->io);
202     array_add(listeners, tmp);
203     return 0;
204 }
205
206 server_t *server_register(int fd, run_client_t runner, void *data)
207 {
208     if (fd < 0) {
209         return NULL;
210     }
211
212     server_t *tmp   = server_acquire();
213     tmp->fd         = fd;
214     tmp->data       = data;
215     tmp->run        = runner;
216     tmp->clear_data = NULL;
217     ev_io_init(&tmp->io, client_cb, tmp->fd, EV_READ);
218     ev_io_start(global_loop, &tmp->io);
219     return tmp;
220 }
221
222 static void refresh_cb(EV_P_ struct ev_signal *w, int event)
223 {
224     if (!config_refresh(config_ptr)) {
225         ev_unloop(EV_A_ EVUNLOOP_ALL);
226     }
227 }
228
229 static void exit_cb(EV_P_ struct ev_signal *w, int event)
230 {
231     ev_unloop(EV_A_ EVUNLOOP_ALL);
232 }
233
234 int server_loop(start_client_t starter, delete_client_t deleter,
235                 run_client_t runner, refresh_t refresh, void* config)
236 {
237     struct ev_signal ev_sighup;
238     struct ev_signal ev_sigint;
239     struct ev_signal ev_sigterm;
240
241     client_start   = starter;
242     client_delete  = deleter;
243     client_run     = runner;
244     config_refresh = refresh;
245     config_ptr     = config;
246
247     if (refresh != NULL) {
248         ev_signal_init(&ev_sighup, refresh_cb, SIGHUP);
249         ev_signal_start(global_loop, &ev_sighup);
250     }
251     ev_signal_init(&ev_sigint, exit_cb, SIGINT);
252     ev_signal_start(global_loop, &ev_sigint);
253     ev_signal_init(&ev_sigterm, exit_cb, SIGTERM);
254     ev_signal_start(global_loop, &ev_sigterm);
255
256     info("entering processing loop");
257     ev_loop(global_loop, 0);
258     info("exit requested");
259     return EXIT_SUCCESS;
260 }