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