1 /*****************************************************************************
2  *  Written by Chris Dunlap <cdunlap@llnl.gov>.
3  *  Copyright (C) 2007-2020 Lawrence Livermore National Security, LLC.
4  *  Copyright (C) 2002-2007 The Regents of the University of California.
5  *  UCRL-CODE-155910.
6  *
7  *  This file is part of the MUNGE Uid 'N' Gid Emporium (MUNGE).
8  *  For details, see <https://dun.github.io/munge/>.
9  *
10  *  MUNGE is free software: you can redistribute it and/or modify it under
11  *  the terms of the GNU General Public License as published by the Free
12  *  Software Foundation, either version 3 of the License, or (at your option)
13  *  any later version.  Additionally for the MUNGE library (libmunge), you
14  *  can redistribute it and/or modify it under the terms of the GNU Lesser
15  *  General Public License as published by the Free Software Foundation,
16  *  either version 3 of the License, or (at your option) any later version.
17  *
18  *  MUNGE is distributed in the hope that it will be useful, but WITHOUT
19  *  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
20  *  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
21  *  and GNU Lesser General Public License for more details.
22  *
23  *  You should have received a copy of the GNU General Public License
24  *  and GNU Lesser General Public License along with MUNGE.  If not, see
25  *  <http://www.gnu.org/licenses/>.
26  *****************************************************************************
27  *  Based on ideas from:
28  *  - David R. Butenhof's "Programming with POSIX Threads" (Section 3.3.4)
29  *  - Jon C. Snader's "Effective TCP/IP Programming" (Tip #20)
30  *****************************************************************************
31  *  Refer to "timer.h" for documentation on public functions.
32  *****************************************************************************/
33 
34 
35 #if HAVE_CONFIG_H
36 #  include <config.h>
37 #endif /* HAVE_CONFIG_H */
38 
39 #include <assert.h>
40 #include <errno.h>
41 #include <pthread.h>
42 #include <signal.h>
43 #include <stdlib.h>
44 #include <sys/time.h>
45 #include <time.h>
46 #include <unistd.h>
47 #include <munge.h>
48 #include "log.h"
49 #include "thread.h"
50 #include "timer.h"
51 
52 
53 /*****************************************************************************
54  *  Private Data Types
55  *****************************************************************************/
56 
57 struct timer {
58     long               id;              /* timer ID                          */
59     struct timespec    ts;              /* expiration time                   */
60     callback_f         f;               /* callback function                 */
61     void              *arg;             /* callback function arg             */
62     struct timer      *next;            /* next timer in list                */
63 };
64 
65 typedef struct timer * timer_p;
66 
67 
68 /*****************************************************************************
69  *  Private Prototypes
70  *****************************************************************************/
71 
72 static void * _timer_thread (void *arg);
73 
74 static void _timer_thread_cleanup (void *arg);
75 
76 static timer_p _timer_alloc (void);
77 
78 static void _timer_get_timespec (struct timespec *tsp);
79 
80 static int _timer_is_timespec_ge (
81         struct timespec *tsp0, struct timespec *tsp1);
82 
83 
84 /*****************************************************************************
85  *  Private Variables
86  *****************************************************************************/
87 
88 static pthread_t       _timer_tid = 0;
89 static pthread_cond_t  _timer_cond = PTHREAD_COND_INITIALIZER;
90 static pthread_mutex_t _timer_mutex = PTHREAD_MUTEX_INITIALIZER;
91 
92 /*  The _timer_id is the ID of the last timer that was set.
93  */
94 static long            _timer_id = 0;
95 
96 /*  The _timer_active list contains timers waiting to be dispatched, sorted in
97  *    order of increasing timespecs; the list head is the next timer to expire.
98  */
99 static timer_p         _timer_active = NULL;
100 
101 /*  The _timer_inactive list contains timers that have been dispatched and can
102  *    be reused without allocating more memory.
103  */
104 static timer_p         _timer_inactive = NULL;
105 
106 
107 /*****************************************************************************
108  *  Public Functions
109  *****************************************************************************/
110 
111 void
timer_init(void)112 timer_init (void)
113 {
114     pthread_attr_t tattr;
115     size_t         stacksize = 256 * 1024;
116 
117     if (_timer_tid != 0) {
118         return;
119     }
120     if ((errno = pthread_attr_init (&tattr)) != 0) {
121         log_errno (EMUNGE_SNAFU, LOG_ERR,
122                 "Failed to init timer thread attribute");
123     }
124 #ifdef _POSIX_THREAD_ATTR_STACKSIZE
125     if ((errno = pthread_attr_setstacksize (&tattr, stacksize)) != 0) {
126         log_errno (EMUNGE_SNAFU, LOG_ERR,
127                 "Failed to set timer thread stacksize");
128     }
129     if ((errno = pthread_attr_getstacksize (&tattr, &stacksize)) != 0) {
130         log_errno (EMUNGE_SNAFU, LOG_ERR,
131                 "Failed to get timer thread stacksize");
132     }
133     log_msg (LOG_DEBUG, "Set timer thread stacksize to %d", (int) stacksize);
134 #endif /* _POSIX_THREAD_ATTR_STACKSIZE */
135 
136     if ((errno = pthread_create (&_timer_tid, &tattr, _timer_thread, NULL))
137             != 0) {
138         log_errno (EMUNGE_SNAFU, LOG_ERR,
139                 "Failed to create timer thread");
140     }
141     if ((errno = pthread_attr_destroy (&tattr)) != 0) {
142         log_errno (EMUNGE_SNAFU, LOG_ERR,
143                 "Failed to destroy timer thread attribute");
144     }
145     return;
146 }
147 
148 
149 void
timer_fini(void)150 timer_fini (void)
151 {
152     void    *result;
153     timer_p *t_prev_ptr;
154     timer_p  t;
155 
156     if (_timer_tid == 0) {
157         return;
158     }
159     if ((errno = pthread_cancel (_timer_tid)) != 0) {
160         log_errno (EMUNGE_SNAFU, LOG_ERR, "Failed to cancel timer thread");
161     }
162     if ((errno = pthread_join (_timer_tid, &result)) != 0) {
163         log_errno (EMUNGE_SNAFU, LOG_ERR, "Failed to join timer thread");
164     }
165     if (result != PTHREAD_CANCELED) {
166         log_err (EMUNGE_SNAFU, LOG_ERR, "Timer thread was not canceled");
167     }
168     _timer_tid = 0;
169 
170     if ((errno = pthread_mutex_lock (&_timer_mutex)) != 0) {
171         log_errno (EMUNGE_SNAFU, LOG_ERR, "Failed to lock timer mutex");
172     }
173     /*  Cancel pending timers by moving active timers to the inactive list.
174      */
175     if (_timer_active) {
176         t_prev_ptr = &_timer_active;
177         while (*t_prev_ptr) {
178             t_prev_ptr = &(*t_prev_ptr)->next;
179         }
180         *t_prev_ptr = _timer_inactive;
181         _timer_inactive = _timer_active;
182         _timer_active = NULL;
183     }
184     /*  De-allocate timers.
185      */
186     while (_timer_inactive) {
187         t = _timer_inactive;
188         _timer_inactive = _timer_inactive->next;
189         free (t);
190     }
191     if ((errno = pthread_mutex_unlock (&_timer_mutex)) != 0) {
192         log_errno (EMUNGE_SNAFU, LOG_ERR, "Failed to unlock timer mutex");
193     }
194     return;
195 }
196 
197 
198 long
timer_set_absolute(callback_f cb,void * arg,const struct timespec * tsp)199 timer_set_absolute (callback_f cb, void *arg, const struct timespec *tsp)
200 {
201     timer_p  t;
202     timer_p *t_prev_ptr;
203     int      do_signal = 0;
204 
205     if (!cb || !tsp) {
206         errno = EINVAL;
207         return (-1);
208     }
209     if ((errno = pthread_mutex_lock (&_timer_mutex)) != 0) {
210         log_errno (EMUNGE_SNAFU, LOG_ERR, "Failed to lock timer mutex");
211     }
212     if (!(t = _timer_alloc ())) {
213         log_errno (EMUNGE_NO_MEMORY, LOG_ERR, "Failed to allocate timer");
214     }
215     /*  Initialize the timer.
216      */
217     _timer_id++;
218     if (_timer_id <= 0) {
219         _timer_id = 1;
220     }
221     t->id = _timer_id;
222     t->f = cb;
223     t->arg = arg;
224     t->ts = *tsp;
225 
226     /*  Insert the timer into the active list.
227      */
228     t_prev_ptr = &_timer_active;
229     while (*t_prev_ptr && _timer_is_timespec_ge (&t->ts, &(*t_prev_ptr)->ts)) {
230         t_prev_ptr = &(*t_prev_ptr)->next;
231     }
232     t->next = *t_prev_ptr;
233     *t_prev_ptr = t;
234 
235     /*  Only signal the timer thread if the active timer has changed.
236      *  Set a flag here so the signal can be done outside the monitor lock.
237      */
238     if (t_prev_ptr == &_timer_active) {
239         do_signal = 1;
240     }
241     if ((errno = pthread_mutex_unlock (&_timer_mutex)) != 0) {
242         log_errno (EMUNGE_SNAFU, LOG_ERR, "Failed to unlock timer mutex");
243     }
244     if (do_signal) {
245         if ((errno = pthread_cond_signal (&_timer_cond)) != 0) {
246             log_errno (EMUNGE_SNAFU, LOG_ERR,
247                     "Failed to signal timer condition");
248         }
249     }
250     assert (t->id > 0);
251     return (t->id);
252 }
253 
254 
255 long
timer_set_relative(callback_f cb,void * arg,long msec)256 timer_set_relative (callback_f cb, void *arg, long msec)
257 {
258     struct timespec ts;
259 
260     /*  Convert the relative time offset into an absolute timespec from now.
261      */
262     _timer_get_timespec (&ts);
263 
264     if (msec > 0) {
265         ts.tv_sec += msec / 1000;
266         ts.tv_nsec += (msec % 1000) * 1000000;
267         if (ts.tv_nsec >= 1000000000) {
268             ts.tv_sec += ts.tv_nsec / 1000000000;
269             ts.tv_nsec %= 1000000000;
270         }
271     }
272     return (timer_set_absolute (cb, arg, &ts));
273 }
274 
275 
276 int
timer_cancel(long id)277 timer_cancel (long id)
278 {
279     timer_p *t_prev_ptr;
280     timer_p  t = NULL;
281     int      do_signal = 0;
282 
283     if (id <= 0) {
284         errno = EINVAL;
285         return (-1);
286     }
287     if ((errno = pthread_mutex_lock (&_timer_mutex)) != 0) {
288         log_errno (EMUNGE_SNAFU, LOG_ERR, "Failed to lock timer mutex");
289     }
290     /*  Locate the active timer specified by [id].
291      */
292     t_prev_ptr = &_timer_active;
293     while (*t_prev_ptr && (id != (*t_prev_ptr)->id)) {
294         t_prev_ptr = &(*t_prev_ptr)->next;
295     }
296     /*  Remove the located timer from the active list.
297      */
298     if (*t_prev_ptr) {
299         t = *t_prev_ptr;
300         *t_prev_ptr = t->next;
301         t->next = _timer_inactive;
302         _timer_inactive = t;
303         /*
304          *  Only signal the timer thread if the active timer was canceled.
305          *  Set a flag here so the signal can be done outside the monitor lock.
306          */
307         if (t_prev_ptr == &_timer_active) {
308             do_signal = 1;
309         }
310     }
311     if ((errno = pthread_mutex_unlock (&_timer_mutex)) != 0) {
312         log_errno (EMUNGE_SNAFU, LOG_ERR, "Failed to unlock timer mutex");
313     }
314     if (do_signal) {
315         if ((errno = pthread_cond_signal (&_timer_cond)) != 0) {
316             log_errno (EMUNGE_SNAFU, LOG_ERR,
317                 "Failed to signal timer condition");
318         }
319     }
320     return (t ? 1 : 0);
321 }
322 
323 
324 /*****************************************************************************
325  *  Private Functions
326  *****************************************************************************/
327 
328 static void *
_timer_thread(void * arg)329 _timer_thread (void *arg)
330 {
331 /*  The timer thread.  It waits until the next active timer expires,
332  *    at which point it invokes the timer's callback function.
333  */
334     sigset_t         sigset;
335     int              cancel_state;
336     struct timespec  ts_now;
337     timer_p         *t_prev_ptr;
338     timer_p          timer_expired;
339 
340     if (sigfillset (&sigset)) {
341         log_errno (EMUNGE_SNAFU, LOG_ERR, "Failed to init timer sigset");
342     }
343     if (pthread_sigmask (SIG_SETMASK, &sigset, NULL) != 0) {
344         log_errno (EMUNGE_SNAFU, LOG_ERR, "Failed to set timer sigset");
345     }
346     if ((errno = pthread_mutex_lock (&_timer_mutex)) != 0) {
347         log_errno (EMUNGE_SNAFU, LOG_ERR, "Failed to lock timer mutex");
348     }
349     pthread_cleanup_push (_timer_thread_cleanup, NULL);
350 
351     for (;;) {
352         /*
353          *  Wait until a timer has been added to the active list.
354          */
355         while (!_timer_active) {
356             /*
357              *  Cancellation point.
358              */
359             if ((errno = pthread_cond_wait (&_timer_cond, &_timer_mutex)) != 0)
360             {
361                 log_errno (EMUNGE_SNAFU, LOG_ERR,
362                         "Failed to wait on timer condition");
363             }
364         }
365         /*  Disable the thread's cancellation state in case any
366          *    callback functions contain cancellation points.
367          */
368         errno = pthread_setcancelstate (PTHREAD_CANCEL_DISABLE, &cancel_state);
369         if (errno != 0) {
370             log_errno (EMUNGE_SNAFU, LOG_ERR,
371                     "Failed to disable timer thread cancellation");
372         }
373         _timer_get_timespec (&ts_now);
374 
375         /*  Select expired timers.
376          */
377         t_prev_ptr = &_timer_active;
378         while (*t_prev_ptr
379                 && _timer_is_timespec_ge (&ts_now, &(*t_prev_ptr)->ts)) {
380             t_prev_ptr = &(*t_prev_ptr)->next;
381         }
382         if (t_prev_ptr != &_timer_active) {
383             /*
384              *  Move expired timers from the active list onto an expired list.
385              *  All expired timers are dispatched before the active list is
386              *    rescanned.  This protects against an erroneous ts_now set in
387              *    the future from causing recurring timers to be continually
388              *    dispatched since ts_now will be requeried once the expired
389              *    list is processed.  (Issue 15)
390              */
391             timer_expired = _timer_active;
392             _timer_active = *t_prev_ptr;
393             *t_prev_ptr = NULL;
394             /*
395              *  Unlock the mutex while dispatching callback functions in case
396              *    any need to set/cancel timers.
397              */
398             if ((errno = pthread_mutex_unlock (&_timer_mutex)) != 0) {
399                 log_errno (EMUNGE_SNAFU, LOG_ERR,
400                         "Failed to unlock timer mutex");
401             }
402             /*  Dispatch expired timers.
403              */
404             t_prev_ptr = &timer_expired;
405             while (*t_prev_ptr) {
406                 (*t_prev_ptr)->f ((*t_prev_ptr)->arg);
407                 t_prev_ptr = &(*t_prev_ptr)->next;
408             }
409             if ((errno = pthread_mutex_lock (&_timer_mutex)) != 0) {
410                 log_errno (EMUNGE_SNAFU, LOG_ERR,
411                         "Failed to lock timer mutex");
412             }
413             /*  Move the expired timers onto the inactive list.
414              *  At the end of the previous while-loop, t_prev_ptr is the
415              *    address of the terminating NULL of the timer_expired list.
416              */
417             *t_prev_ptr = _timer_inactive;
418             _timer_inactive = timer_expired;
419         }
420         /*  Enable the thread's cancellation state.
421          *  Since enabling cancellation is not a cancellation point,
422          *    a pending cancel request must be tested for.  But a
423          *    pthread_testcancel() is not needed here.  If active timers
424          *    are present, the pthread_cond_timedwait() at the bottom of
425          *    the for-loop will serve as the cancellation point; otherwise,
426          *    the pthread_cond_wait() at the top of the for-loop will.
427          */
428         errno = pthread_setcancelstate (cancel_state, &cancel_state);
429         if (errno != 0) {
430             log_errno (EMUNGE_SNAFU, LOG_ERR,
431                     "Failed to enable timer thread cancellation");
432         }
433         /*  Wait until the next active timer is set to expire,
434          *    or until the active timer changes.
435          */
436         while (_timer_active) {
437             /*
438              *  Cancellation point.
439              */
440             errno = pthread_cond_timedwait (
441                     &_timer_cond, &_timer_mutex, &(_timer_active->ts));
442 
443             if (errno == EINTR) {
444                 continue;
445             }
446             if ((errno == ETIMEDOUT) || (errno == 0)) {
447                 break;
448             }
449             log_errno (EMUNGE_SNAFU, LOG_ERR,
450                     "Failed to wait on timer condition");
451         }
452     }
453     assert (1);                         /* not reached */
454     pthread_cleanup_pop (1);
455     return (NULL);
456 }
457 
458 
459 static void
_timer_thread_cleanup(void * arg)460 _timer_thread_cleanup (void *arg)
461 {
462 /*  The cleanup routine for the timer thread.
463  *    It ensures the mutex is released when the thread is canceled.
464  */
465     if ((errno = pthread_mutex_unlock (&_timer_mutex)) != 0) {
466         log_errno (EMUNGE_SNAFU, LOG_ERR, "Failed to unlock timer mutex");
467     }
468     return;
469 }
470 
471 
472 static timer_p
_timer_alloc(void)473 _timer_alloc (void)
474 {
475 /*  Returns a new timer, or NULL on memory allocation failure.
476  *  The mutex must be locked before calling this routine.
477  */
478     timer_p t;
479 
480     assert (lsd_mutex_is_locked (&_timer_mutex));
481 
482     if (_timer_inactive) {
483         t = _timer_inactive;
484         _timer_inactive = _timer_inactive->next;
485         t->next = NULL;
486     }
487     else {
488         t = malloc (sizeof (struct timer));
489     }
490     return (t);
491 }
492 
493 
494 static void
_timer_get_timespec(struct timespec * tsp)495 _timer_get_timespec (struct timespec *tsp)
496 {
497 /*  Sets the timespec [tsp] to the current time.
498  */
499     struct timeval tv;
500 
501     assert (tsp != NULL);
502 
503     if (gettimeofday (&tv, NULL) < 0) {
504         log_errno (EMUNGE_SNAFU, LOG_ERR, "Failed to query current time");
505     }
506     tsp->tv_sec = tv.tv_sec;
507     tsp->tv_nsec = tv.tv_usec * 1000;
508     return;
509 }
510 
511 
512 static int
_timer_is_timespec_ge(struct timespec * tsp0,struct timespec * tsp1)513 _timer_is_timespec_ge (struct timespec *tsp0, struct timespec *tsp1)
514 {
515 /*  Returns non-zero if the time specified by [tsp0] is
516  *    greater than or equal to the time specified by [tsp1].
517  */
518     assert (tsp0 != NULL);
519     assert (tsp1 != NULL);
520 
521     if (tsp0->tv_nsec >= 1000000000) {
522         tsp0->tv_sec += tsp0->tv_nsec / 1000000000;
523         tsp0->tv_nsec %= 1000000000;
524     }
525     if (tsp1->tv_nsec >= 1000000000) {
526         tsp1->tv_sec += tsp1->tv_nsec / 1000000000;
527         tsp1->tv_nsec %= 1000000000;
528     }
529     if (tsp0->tv_sec == tsp1->tv_sec) {
530         return (tsp0->tv_nsec >= tsp1->tv_nsec);
531     }
532     else {
533         return (tsp0->tv_sec >= tsp1->tv_sec);
534     }
535 }
536