1 /*
2 *
3 ***** BEGIN LICENSE BLOCK *****
4
5 Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
6 Copyright (C) 2017-2019 Olof Hagsand
7 Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC(Netgat)e
8
9 This file is part of CLIXON.
10
11 Licensed under the Apache License, Version 2.0 (the "License");
12 you may not use this file except in compliance with the License.
13 You may obtain a copy of the License at
14
15 http://www.apache.org/licenses/LICENSE-2.0
16
17 Unless required by applicable law or agreed to in writing, software
18 distributed under the License is distributed on an "AS IS" BASIS,
19 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20 See the License for the specific language governing permissions and
21 limitations under the License.
22
23 Alternatively, the contents of this file may be used under the terms of
24 the GNU General Public License Version 3 or later (the "GPL"),
25 in which case the provisions of the GPL are applicable instead
26 of those above. If you wish to allow use of your version of this file only
27 under the terms of the GPL, and not to allow others to
28 use your version of this file under the terms of Apache License version 2,
29 indicate your decision by deleting the provisions above and replace them with
30 the notice and other provisions required by the GPL. If you do not delete
31 the provisions above, a recipient may use your version of this file under
32 the terms of any one of the Apache License version 2 or the GPL.
33
34 ***** END LICENSE BLOCK *****
35
36 *
37 * Event handling and loop
38 */
39
40 #ifdef HAVE_CONFIG_H
41 #include "clixon_config.h" /* generated by config & autoconf */
42 #endif
43
44 #include <stdio.h>
45 #include <stdlib.h>
46 #include <stdint.h>
47 #include <fcntl.h>
48 #include <unistd.h>
49 #include <errno.h>
50 #include <string.h>
51 #include <syslog.h>
52 #include <sys/types.h>
53 #include <sys/time.h>
54
55 #include "clixon_queue.h"
56 #include "clixon_log.h"
57 #include "clixon_err.h"
58 #include "clixon_event.h"
59
60 /*
61 * Constants
62 */
63 #define EVENT_STRLEN 32
64
65 /*
66 * Types
67 */
68 struct event_data{
69 struct event_data *e_next; /* next in list */
70 int (*e_fn)(int, void*); /* function */
71 enum {EVENT_FD, EVENT_TIME} e_type; /* type of event */
72 int e_fd; /* File descriptor */
73 struct timeval e_time; /* Timeout */
74 void *e_arg; /* function argument */
75 char e_string[EVENT_STRLEN]; /* string for debugging */
76 };
77
78 /*
79 * Internal variables
80 * XXX consider use handle variables instead of global
81 */
82 static struct event_data *ee = NULL;
83 static struct event_data *ee_timers = NULL;
84
85 /* Set if element in ee is deleted (clixon_event_unreg_fd). Check in ee loops */
86 static int _ee_unreg = 0;
87
88 static int _clicon_exit = 0;
89
90 /*! For signal handlers: instead of doing exit, set a global variable to exit
91 * Status is then checked in event_loop.
92 * Note it maybe would be better to do use on a handle basis, but a signal
93 * handler is global
94 */
95 int
clicon_exit_set(void)96 clicon_exit_set(void)
97 {
98 _clicon_exit++;
99 return 0;
100 }
101
102 /*! Set exit to 0
103 */
104 int
clicon_exit_reset(void)105 clicon_exit_reset(void)
106 {
107 _clicon_exit = 0;
108 return 0;
109 }
110
111 /*! Get the status of global exit variable, usually set by signal handlers
112 */
113 int
clicon_exit_get(void)114 clicon_exit_get(void)
115 {
116 return _clicon_exit;
117 }
118
119 /*! Register a callback function to be called on input on a file descriptor.
120 *
121 * @param[in] fd File descriptor
122 * @param[in] fn Function to call when input available on fd
123 * @param[in] arg Argument to function fn
124 * @param[in] str Describing string for logging
125 * @code
126 * int fn(int fd, void *arg){
127 * }
128 * clixon_event_reg_fd(fd, fn, (void*)42, "call fn on input on fd");
129 * @endcode
130 */
131 int
clixon_event_reg_fd(int fd,int (* fn)(int,void *),void * arg,char * str)132 clixon_event_reg_fd(int fd,
133 int (*fn)(int, void*),
134 void *arg,
135 char *str)
136 {
137 struct event_data *e;
138
139 if ((e = (struct event_data *)malloc(sizeof(struct event_data))) == NULL){
140 clicon_err(OE_EVENTS, errno, "malloc");
141 return -1;
142 }
143 memset(e, 0, sizeof(struct event_data));
144 strncpy(e->e_string, str, EVENT_STRLEN);
145 e->e_fd = fd;
146 e->e_fn = fn;
147 e->e_arg = arg;
148 e->e_type = EVENT_FD;
149 e->e_next = ee;
150 ee = e;
151 clicon_debug(2, "%s, registering %s", __FUNCTION__, e->e_string);
152 return 0;
153 }
154
155 /*! Deregister a file descriptor callback
156 * @param[in] s File descriptor
157 * @param[in] fn Function to call when input available on fd
158 * Note: deregister when exactly function and socket match, not argument
159 * @see clixon_event_reg_fd
160 * @see clixon_event_unreg_timeout
161 */
162 int
clixon_event_unreg_fd(int s,int (* fn)(int,void *))163 clixon_event_unreg_fd(int s,
164 int (*fn)(int, void*))
165 {
166 struct event_data *e, **e_prev;
167 int found = 0;
168
169 e_prev = ⅇ
170 for (e = ee; e; e = e->e_next){
171 if (fn == e->e_fn && s == e->e_fd) {
172 found++;
173 *e_prev = e->e_next;
174 _ee_unreg++;
175 free(e);
176 break;
177 }
178 e_prev = &e->e_next;
179 }
180 return found?0:-1;
181 }
182
183 /*! Call a callback function at an absolute time
184 * @param[in] t Absolute (not relative!) timestamp when callback is called
185 * @param[in] fn Function to call at time t
186 * @param[in] arg Argument to function fn
187 * @param[in] str Describing string for logging
188 * @code
189 * int fn(int d, void *arg){
190 * struct timeval t, t1;
191 * gettimeofday(&t, NULL);
192 * t1.tv_sec = 1; t1.tv_usec = 0;
193 * timeradd(&t, &t1, &t);
194 * clixon_event_reg_timeout(t, fn, NULL, "call every second");
195 * }
196 * @endcode
197 *
198 * Note that the timestamp is an absolute timestamp, not relative.
199 * Note also that the callback is not periodic, you need to make a new
200 * registration for each period, see example above.
201 * Note also that the first argument to fn is a dummy, just to get the same
202 * signatute as for file-descriptor callbacks.
203 * @see clixon_event_reg_fd
204 * @see clixon_event_unreg_timeout
205 */
206 int
clixon_event_reg_timeout(struct timeval t,int (* fn)(int,void *),void * arg,char * str)207 clixon_event_reg_timeout(struct timeval t,
208 int (*fn)(int, void*),
209 void *arg,
210 char *str)
211 {
212 struct event_data *e, *e1, **e_prev;
213
214 if ((e = (struct event_data *)malloc(sizeof(struct event_data))) == NULL){
215 clicon_err(OE_EVENTS, errno, "malloc");
216 return -1;
217 }
218 memset(e, 0, sizeof(struct event_data));
219 strncpy(e->e_string, str, EVENT_STRLEN);
220 e->e_fn = fn;
221 e->e_arg = arg;
222 e->e_type = EVENT_TIME;
223 e->e_time = t;
224 /* Sort into right place */
225 e_prev = &ee_timers;
226 for (e1=ee_timers; e1; e1=e1->e_next){
227 if (timercmp(&e->e_time, &e1->e_time, <))
228 break;
229 e_prev = &e1->e_next;
230 }
231 e->e_next = e1;
232 *e_prev = e;
233 clicon_debug(2, "%s: %s", __FUNCTION__, str);
234 return 0;
235 }
236
237 /*! Deregister a timeout callback as previosly registered by clixon_event_reg_timeout()
238 * Note: deregister when exactly function and function arguments match, not time. So you
239 * cannot have same function and argument callback on different timeouts. This is a little
240 * different from clixon_event_unreg_fd.
241 * @param[in] fn Function to call at time t
242 * @param[in] arg Argument to function fn
243 * @see clixon_event_reg_timeout
244 * @see clixon_event_unreg_fd
245 */
246 int
clixon_event_unreg_timeout(int (* fn)(int,void *),void * arg)247 clixon_event_unreg_timeout(int (*fn)(int, void*),
248 void *arg)
249 {
250 struct event_data *e, **e_prev;
251 int found = 0;
252
253 e_prev = &ee_timers;
254 for (e = ee_timers; e; e = e->e_next){
255 if (fn == e->e_fn && arg == e->e_arg) {
256 found++;
257 *e_prev = e->e_next;
258 free(e);
259 break;
260 }
261 e_prev = &e->e_next;
262 }
263 return found?0:-1;
264 }
265
266 /*! Poll to see if there is any data available on this file descriptor.
267 * @param[in] fd File descriptor
268 * @retval -1 Error
269 * @retval 0 Nothing to read/empty fd
270 * @retval 1 Something to read on fd
271 */
272 int
clixon_event_poll(int fd)273 clixon_event_poll(int fd)
274 {
275 int retval = -1;
276 fd_set fdset;
277 struct timeval tnull = {0,};
278
279 FD_ZERO(&fdset);
280 FD_SET(fd, &fdset);
281 if ((retval = select(FD_SETSIZE, &fdset, NULL, NULL, &tnull)) < 0)
282 clicon_err(OE_EVENTS, errno, "select");
283 return retval;
284 }
285
286 /*! Dispatch file descriptor events (and timeouts) by invoking callbacks.
287 * There is an issue with fairness that timeouts may take over all events
288 * One could try to poll the file descriptors after a timeout?
289 * @retval 0 OK
290 * @retval -1 Error: eg select, callback, timer,
291 */
292 int
clixon_event_loop(void)293 clixon_event_loop(void)
294 {
295 struct event_data *e;
296 struct event_data *e_next;
297 int n;
298 struct timeval t;
299 struct timeval t0;
300 struct timeval tnull = {0,};
301 fd_set fdset;
302 int retval = -1;
303
304 while (!clicon_exit_get()){
305 FD_ZERO(&fdset);
306 for (e=ee; e; e=e->e_next)
307 if (e->e_type == EVENT_FD)
308 FD_SET(e->e_fd, &fdset);
309 if (ee_timers != NULL){
310 gettimeofday(&t0, NULL);
311 timersub(&ee_timers->e_time, &t0, &t);
312 if (t.tv_sec < 0)
313 n = select(FD_SETSIZE, &fdset, NULL, NULL, &tnull);
314 else
315 n = select(FD_SETSIZE, &fdset, NULL, NULL, &t);
316 }
317 else
318 n = select(FD_SETSIZE, &fdset, NULL, NULL, NULL);
319 if (clicon_exit_get())
320 break;
321 if (n == -1) {
322 if (errno == EINTR){
323 clicon_debug(1, "%s select: %s", __FUNCTION__, strerror(errno));
324 clicon_err(OE_EVENTS, errno, "select");
325 retval = 0;
326 }
327 else
328 clicon_err(OE_EVENTS, errno, "select");
329 goto err;
330 }
331 if (n==0){ /* Timeout */
332 e = ee_timers;
333 ee_timers = ee_timers->e_next;
334 clicon_debug(2, "%s timeout: %s", __FUNCTION__, e->e_string);
335 if ((*e->e_fn)(0, e->e_arg) < 0){
336 free(e);
337 goto err;
338 }
339 free(e);
340 }
341 _ee_unreg = 0;
342 for (e=ee; e; e=e_next){
343 if (clicon_exit_get())
344 break;
345 e_next = e->e_next;
346 if(e->e_type == EVENT_FD && FD_ISSET(e->e_fd, &fdset)){
347 clicon_debug(2, "%s: FD_ISSET: %s", __FUNCTION__, e->e_string);
348 if ((*e->e_fn)(e->e_fd, e->e_arg) < 0){
349 clicon_debug(1, "%s Error in: %s", __FUNCTION__, e->e_string);
350 goto err;
351
352 }
353 if (_ee_unreg){
354 _ee_unreg = 0;
355 break;
356 }
357 }
358 }
359 continue;
360 err:
361 break;
362 }
363 clicon_debug(1, "%s done:%d", __FUNCTION__, retval);
364 return retval;
365 }
366
367 int
clixon_event_exit(void)368 clixon_event_exit(void)
369 {
370 struct event_data *e, *e_next;
371
372 e_next = ee;
373 while ((e = e_next) != NULL){
374 e_next = e->e_next;
375 free(e);
376 }
377 ee = NULL;
378 e_next = ee_timers;
379 while ((e = e_next) != NULL){
380 e_next = e->e_next;
381 free(e);
382 }
383 ee_timers = NULL;
384 return 0;
385 }
386