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 = &ee;
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