Coverage Report

Created: 2025-07-12 06:57

/src/proftpd/src/timers.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * ProFTPD - FTP server daemon
3
 * Copyright (c) 1997, 1998 Public Flood Software
4
 * Copyright (c) 2001-2025 The ProFTPD Project team
5
 *
6
 * This program is free software; you can redistribute it and/or modify
7
 * it under the terms of the GNU General Public License as published by
8
 * the Free Software Foundation; either version 2 of the License, or
9
 * (at your option) any later version.
10
 *
11
 * This program is distributed in the hope that it will be useful,
12
 * BUT witHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 * GNU General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU General Public License
17
 * along with this program; if not, write to the Free Software
18
 * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
19
 *
20
 * As a special exemption, Public Flood Software/MacGyver aka Habeeb J. Dihu
21
 * and other respective copyright holders give permission to link this program
22
 * with OpenSSL, and distribute the resulting executable, without including
23
 * the source code for OpenSSL in the source distribution.
24
 */
25
26
/* Timer system, based on alarm() and SIGALRM. */
27
28
#include "conf.h"
29
30
/* From src/main.c */
31
extern volatile unsigned int recvd_signal_flags;
32
33
struct timer {
34
  struct timer *next, *prev;
35
36
  long count;                   /* Amount of time remaining */
37
  long interval;                /* Original length of timer */
38
39
  int timerno;                  /* Caller dependent timer number */
40
  module *mod;                  /* Module owning this timer */
41
  callback_t callback;          /* Function to callback */
42
  char remove;                  /* Internal use */
43
44
  const char *desc;   /* Description of timer, provided by caller */
45
};
46
47
0
#define PR_TIMER_DYNAMIC_TIMERNO  1024
48
49
static int current_timeout = 0;
50
static int total_time = 0;
51
static int sleep_sem = 0;
52
static int alarms_blocked = 0, alarm_pending = 0;
53
static xaset_t *timers = NULL;
54
static xaset_t *recycled = NULL;
55
static xaset_t *free_timers = NULL;
56
static int _indispatch = 0;
57
static int dynamic_timerno = PR_TIMER_DYNAMIC_TIMERNO;
58
static unsigned int nalarms = 0;
59
static time_t alarmed_time = 0;
60
61
static pool *timer_pool = NULL;
62
63
static const char *trace_channel = "timer";
64
65
0
static int timer_cmp(struct timer *t1, struct timer *t2) {
66
0
  if (t1->count < t2->count) {
67
0
    return -1;
68
0
  }
69
70
0
  if (t1->count > t2->count) {
71
0
    return 1;
72
0
  }
73
74
0
  return 0;
75
0
}
76
77
/* This function does the work of iterating through the list of registered
78
 * timers, checking to see if their callbacks should be invoked and whether
79
 * they should be removed from the registration list. Its return value is
80
 * the amount of time remaining on the first timer in the list.
81
 */
82
0
static int process_timers(int elapsed) {
83
0
  struct timer *t = NULL, *next = NULL;
84
0
  int res = 0;
85
86
0
  if (recycled == NULL) {
87
0
    recycled = xaset_create(timer_pool, NULL);
88
0
  }
89
90
0
  if (elapsed == 0 &&
91
0
      recycled->xas_list == NULL) {
92
0
    if (timers == NULL) {
93
0
      return 0;
94
0
    }
95
96
0
    if (timers->xas_list != NULL) {
97
      /* The value we return is a proposed timeout, for the next call to
98
       * alarm(3).  We start with the simple count of timers in our list.
99
       *
100
       * But then we reduce the number; some of the timers' intervals may
101
       * less than the number of total timers.
102
       */
103
0
      res = ((struct timer *) timers->xas_list)->count;
104
0
      if (res > 5) {
105
0
        res = 5;
106
0
      }
107
0
    }
108
109
0
    return res;
110
0
  }
111
112
  /* Critical code, no interruptions please */
113
0
  if (_indispatch) {
114
0
    return 0;
115
0
  }
116
117
0
  pr_alarms_block();
118
0
  _indispatch++;
119
120
0
  if (elapsed) {
121
0
    for (t = (struct timer *) timers->xas_list; t; t = next) {
122
      /* If this timer has already been handled, skip */
123
0
      next = t->next;
124
125
0
      if (t->remove) {
126
        /* Move the timer onto the free_timers chain, for later reuse. */
127
0
        xaset_remove(timers, (xasetmember_t *) t);
128
0
        xaset_insert(free_timers, (xasetmember_t *) t);
129
130
0
      } else if ((t->count -= elapsed) <= 0) {
131
        /* This timer's interval has elapsed, so trigger its callback. */
132
133
0
        pr_trace_msg(trace_channel, 4,
134
0
          "%ld %s for timer ID %d ('%s', for module '%s') elapsed, invoking "
135
0
          "callback (%p)", t->interval,
136
0
          t->interval != 1 ? "seconds" : "second", t->timerno,
137
0
          t->desc ? t->desc : "<unknown>",
138
0
          t->mod ? t->mod->name : "<none>", t->callback);
139
140
0
        if (t->callback(t->interval, t->timerno, t->interval - t->count,
141
0
            t->mod) == 0) {
142
143
          /* A return value of zero means this timer is done, and can be
144
           * removed.
145
           */
146
0
          xaset_remove(timers, (xasetmember_t *) t);
147
0
          xaset_insert(free_timers, (xasetmember_t *) t);
148
149
0
        } else {
150
          /* A non-zero return value from a timer callback signals that
151
           * the timer should be reused/restarted.
152
           */
153
0
          pr_trace_msg(trace_channel, 6,
154
0
            "restarting timer ID %d ('%s'), as per callback", t->timerno,
155
0
            t->desc ? t->desc : "<unknown>");
156
157
0
          xaset_remove(timers, (xasetmember_t *) t);
158
0
          t->count = t->interval;
159
0
          xaset_insert(recycled, (xasetmember_t *) t);
160
0
        }
161
0
      }
162
0
    }
163
0
  }
164
165
  /* Put the recycled timers back into the main timer list. */
166
0
  t = (struct timer *) recycled->xas_list;
167
0
  while (t != NULL) {
168
0
    pr_signals_handle();
169
170
0
    xaset_remove(recycled, (xasetmember_t *) t);
171
0
    xaset_insert_sort(timers, (xasetmember_t *) t, TRUE);
172
0
    t = (struct timer *) recycled->xas_list;
173
0
  }
174
175
0
  _indispatch--;
176
0
  pr_alarms_unblock();
177
178
  /* If no active timers remain in the list, there is no reason to set the
179
   * SIGALRM handle.
180
   */
181
182
0
  if (timers->xas_list != NULL) {
183
    /* The value we return is a proposed timeout, for the next call to
184
     * alarm(3).  We start with the simple count of timers in our list.
185
     *
186
     * But then we reduce the number; some of the timers' intervals may
187
     * less than the number of total timers.
188
     */
189
0
    res = ((struct timer *) timers->xas_list)->count;
190
0
    if (res > 5) {
191
0
      res = 5;
192
0
    }
193
0
  }
194
195
0
  return res;
196
0
}
197
198
0
static RETSIGTYPE sig_alarm(int signo) {
199
0
  struct sigaction act;
200
201
0
  act.sa_handler = sig_alarm;
202
0
  sigemptyset(&act.sa_mask);
203
0
  act.sa_flags = 0;
204
205
0
#ifdef SA_INTERRUPT
206
0
  act.sa_flags |= SA_INTERRUPT;
207
0
#endif
208
209
  /* Install this handler for SIGALRM. */
210
0
  if (sigaction(SIGALRM, &act, NULL) < 0) {
211
0
    pr_log_pri(PR_LOG_WARNING,
212
0
      "unable to install SIGALRM handler via sigaction(2): %s",
213
0
      strerror(errno));
214
0
  }
215
216
0
#ifdef HAVE_SIGINTERRUPT
217
0
  if (siginterrupt(SIGALRM, 1) < 0) {
218
0
    pr_log_pri(PR_LOG_WARNING,
219
0
      "unable to allow SIGALRM to interrupt system calls: %s", strerror(errno));
220
0
  }
221
0
#endif
222
223
0
  recvd_signal_flags |= RECEIVED_SIG_ALRM;
224
0
  nalarms++;
225
226
  /* Reset the alarm */
227
0
  total_time += current_timeout;
228
0
  if (current_timeout) {
229
0
    alarmed_time = time(NULL);
230
0
    alarm(current_timeout);
231
0
  }
232
0
}
233
234
0
static void set_sig_alarm(void) {
235
0
  struct sigaction act;
236
237
0
  act.sa_handler = sig_alarm;
238
0
  sigemptyset(&act.sa_mask);
239
0
  act.sa_flags = 0;
240
0
#ifdef SA_INTERRUPT
241
0
  act.sa_flags |= SA_INTERRUPT;
242
0
#endif
243
244
  /* Install this handler for SIGALRM. */
245
0
  if (sigaction(SIGALRM, &act, NULL) < 0) {
246
0
    pr_log_pri(PR_LOG_WARNING,
247
0
      "unable to install SIGALRM handler via sigaction(2): %s",
248
0
      strerror(errno));
249
0
  }
250
251
0
#ifdef HAVE_SIGINTERRUPT
252
0
  if (siginterrupt(SIGALRM, 1) < 0) {
253
0
    pr_log_pri(PR_LOG_WARNING,
254
0
      "unable to allow SIGALRM to interrupt system calls: %s", strerror(errno));
255
0
  }
256
0
#endif
257
0
}
258
259
0
void handle_alarm(void) {
260
  /* We need to adjust for any time that might be remaining on the alarm,
261
   * in case we were called in order to change alarm durations.  Note
262
   * that rapid-fire calling of this function will probably screw
263
   * up the already poor resolution of alarm() _horribly_.  Oh well,
264
   * this shouldn't be used for any precise work anyway, it's only
265
   * for modules to perform approximate timing.
266
   */
267
268
  /* It's possible that alarms are blocked when this function is
269
   * called, if so, increment alarm_pending and exit swiftly.
270
   */
271
0
  while (nalarms) {
272
0
    nalarms = 0;
273
274
0
    if (!alarms_blocked) {
275
0
      int alarm_elapsed, new_timeout;
276
0
      time_t now;
277
278
      /* Clear any pending ALRM signals. */
279
0
      alarm(0);
280
281
      /* Determine how much time has elapsed since we last processed timers. */
282
0
      time(&now);
283
0
      alarm_elapsed = alarmed_time > 0 ? (int) (now - alarmed_time) : 0;
284
285
0
      new_timeout = total_time + alarm_elapsed;
286
0
      total_time = 0;
287
0
      new_timeout = process_timers(new_timeout);
288
289
0
      alarmed_time = now;
290
0
      alarm(current_timeout = new_timeout);
291
292
0
    } else {
293
0
      alarm_pending++;
294
0
    }
295
0
  }
296
297
0
  pr_signals_handle();
298
0
}
299
300
0
int pr_timer_reset(int timerno, module *mod) {
301
0
  struct timer *t = NULL;
302
303
0
  if (timers == NULL) {
304
0
    errno = EPERM;
305
0
    return -1;
306
0
  }
307
308
0
  if (_indispatch) {
309
0
    errno = EINTR;
310
0
    return -1;
311
0
  }
312
313
0
  pr_alarms_block();
314
315
0
  if (recycled == NULL) {
316
0
    recycled = xaset_create(timer_pool, NULL);
317
0
  }
318
319
0
  for (t = (struct timer *) timers->xas_list; t; t = t->next) {
320
0
    if (t->timerno == timerno &&
321
0
        (t->mod == mod || mod == ANY_MODULE)) {
322
0
      t->count = t->interval;
323
0
      xaset_remove(timers, (xasetmember_t *) t);
324
0
      xaset_insert(recycled, (xasetmember_t *) t);
325
0
      nalarms++;
326
327
      /* The handle_alarm() function also readjusts the timers lists
328
       * as part of its processing, so it needs to be called when a timer
329
       * is reset.
330
       */
331
0
      handle_alarm();
332
0
      break;
333
0
    }
334
0
  }
335
336
0
  pr_alarms_unblock();
337
338
0
  if (t != NULL) {
339
0
    pr_trace_msg(trace_channel, 7, "reset timer ID %d ('%s', for module '%s')",
340
0
      t->timerno, t->desc, t->mod ? t->mod->name : "[none]");
341
0
    return t->timerno;
342
0
  }
343
344
0
  return 0;
345
0
}
346
347
0
int pr_timer_remove(int timerno, module *mod) {
348
0
  struct timer *t = NULL, *tnext = NULL;
349
0
  int nremoved = 0;
350
351
  /* If there are no timers currently registered, do nothing. */
352
0
  if (timers == NULL) {
353
0
    return 0;
354
0
  }
355
356
0
  pr_alarms_block();
357
358
0
  for (t = (struct timer *) timers->xas_list; t; t = tnext) {
359
0
    tnext = t->next;
360
361
0
    if ((timerno < 0 || t->timerno == timerno) &&
362
0
        (mod == ANY_MODULE || t->mod == mod)) {
363
0
      nremoved++;
364
365
0
      if (_indispatch) {
366
0
        t->remove++;
367
368
0
      } else {
369
0
        xaset_remove(timers, (xasetmember_t *) t);
370
0
        xaset_insert(free_timers, (xasetmember_t *) t);
371
0
  nalarms++;
372
373
        /* The handle_alarm() function also readjusts the timers lists
374
         * as part of its processing, so it needs to be called when a timer
375
         * is removed.
376
         */
377
0
        handle_alarm();
378
0
      }
379
380
0
      pr_trace_msg(trace_channel, 7,
381
0
        "removed timer ID %d ('%s', for module '%s')", t->timerno, t->desc,
382
0
        t->mod ? t->mod->name : "[none]");
383
0
    }
384
385
    /* If we are removing a specific timer, break out of the loop now.
386
     * Otherwise, keep removing any matching timers.
387
     */
388
0
    if (nremoved > 0 &&
389
0
        timerno >= 0) {
390
0
      break;
391
0
    }
392
0
  }
393
394
0
  pr_alarms_unblock();
395
396
0
  if (nremoved == 0) {
397
0
    errno = ENOENT;
398
0
    return -1;
399
0
  }
400
401
  /* If we removed a specific timer because of the given timerno, return
402
   * that timerno value.
403
   */
404
0
  if (timerno >= 0) {
405
0
    return timerno;
406
0
  }
407
408
0
  return nremoved;
409
0
}
410
411
int pr_timer_add(int seconds, int timerno, module *mod, callback_t cb,
412
0
    const char *desc) {
413
0
  struct timer *t = NULL;
414
415
0
  if (seconds <= 0 ||
416
0
      cb == NULL ||
417
0
      desc == NULL) {
418
0
    errno = EINVAL;
419
0
    return -1;
420
0
  }
421
422
0
  if (timers == NULL) {
423
0
    timers = xaset_create(timer_pool, (XASET_COMPARE) timer_cmp);
424
0
  }
425
426
  /* Check to see that, if specified, the timerno is not already in use. */
427
0
  if (timerno >= 0) {
428
0
    for (t = (struct timer *) timers->xas_list; t; t = t->next) {
429
0
      if (t->timerno == timerno) {
430
0
        errno = EPERM;
431
0
        return -1;
432
0
      }
433
0
    }
434
0
  }
435
436
0
  if (free_timers == NULL) {
437
0
    free_timers = xaset_create(timer_pool, NULL);
438
0
  }
439
440
  /* Try to use an old timer first */
441
0
  pr_alarms_block();
442
0
  t = (struct timer *) free_timers->xas_list;
443
0
  if (t != NULL) {
444
0
    xaset_remove(free_timers, (xasetmember_t *) t);
445
446
0
  } else {
447
0
    if (timer_pool == NULL) {
448
0
      timer_pool = make_sub_pool(permanent_pool);
449
0
      pr_pool_tag(timer_pool, "Timer Pool");
450
0
    }
451
452
    /* Must allocate a new one */
453
0
    t = palloc(timer_pool, sizeof(struct timer));
454
0
  }
455
456
0
  if (timerno < 0) {
457
    /* Dynamic timer */
458
0
    if (dynamic_timerno < PR_TIMER_DYNAMIC_TIMERNO) {
459
0
      dynamic_timerno = PR_TIMER_DYNAMIC_TIMERNO;
460
0
    }
461
462
0
    timerno = dynamic_timerno++;
463
0
  }
464
465
0
  t->timerno = timerno;
466
0
  t->count = t->interval = seconds;
467
0
  t->callback = cb;
468
0
  t->mod = mod;
469
0
  t->remove = 0;
470
0
  t->desc = desc;
471
472
  /* If called while _indispatch, add to the recycled list to prevent
473
   * list corruption
474
   */
475
476
0
  if (_indispatch) {
477
0
    if (recycled == NULL) {
478
0
      recycled = xaset_create(timer_pool, NULL);
479
0
    }
480
0
    xaset_insert(recycled, (xasetmember_t *) t);
481
482
0
  } else {
483
0
    xaset_insert_sort(timers, (xasetmember_t *) t, TRUE);
484
0
    nalarms++;
485
0
    set_sig_alarm();
486
487
    /* The handle_alarm() function also readjusts the timers lists
488
     * as part of its processing, so it needs to be called when a timer
489
     * is added.
490
     */
491
0
    handle_alarm();
492
0
  }
493
494
0
  pr_alarms_unblock();
495
496
0
  pr_trace_msg(trace_channel, 7, "added timer ID %d ('%s', for module '%s'), "
497
0
    "triggering in %ld %s", t->timerno, t->desc,
498
0
    t->mod ? t->mod->name : "[none]", t->interval,
499
0
    t->interval != 1 ? "seconds" : "second");
500
0
  return timerno;
501
0
}
502
503
/* Alarm blocking.  This is done manually rather than with syscalls,
504
 * so as to allow for easier signal handling, portability and
505
 * detecting the number of blocked alarms, as well as nesting the
506
 * block/unblock functions.
507
 */
508
509
15.3k
void pr_alarms_block(void) {
510
15.3k
  ++alarms_blocked;
511
15.3k
}
512
513
15.3k
void pr_alarms_unblock(void) {
514
15.3k
  --alarms_blocked;
515
15.3k
  if (alarms_blocked == 0 && alarm_pending) {
516
0
    alarm_pending = 0;
517
0
    nalarms++;
518
0
    handle_alarm();
519
0
  }
520
15.3k
}
521
522
0
static int sleep_cb(CALLBACK_FRAME) {
523
0
  sleep_sem++;
524
0
  return 0;
525
0
}
526
527
0
int pr_timer_sleep(int seconds) {
528
0
  int timerno = 0;
529
0
  sigset_t oset;
530
531
0
  sleep_sem = 0;
532
533
0
  if (alarms_blocked ||
534
0
      _indispatch) {
535
0
    errno = EPERM;
536
0
    return -1;
537
0
  }
538
539
0
  timerno = pr_timer_add(seconds, -1, NULL, sleep_cb, "sleep");
540
0
  if (timerno == -1) {
541
0
    return -1;
542
0
  }
543
544
0
  sigemptyset(&oset);
545
0
  while (!sleep_sem) {
546
0
    sigsuspend(&oset);
547
0
    handle_alarm();
548
0
  }
549
550
0
  return 0;
551
0
}
552
553
0
int pr_timer_usleep(unsigned long usecs) {
554
0
  struct timeval tv;
555
556
0
  if (usecs == 0) {
557
0
    errno = EINVAL;
558
0
    return -1;
559
0
  }
560
561
0
  tv.tv_sec = (usecs / 1000000);
562
0
  tv.tv_usec = (usecs - (tv.tv_sec * 1000000));
563
564
0
  pr_signals_block();
565
0
  (void) select(0, NULL, NULL, NULL, &tv);
566
0
  pr_signals_unblock();
567
568
0
  return 0;
569
0
}
570
571
0
void timers_init(void) {
572
573
  /* Reset some of the key static variables. */
574
0
  current_timeout = 0;
575
0
  total_time = 0;
576
0
  nalarms = 0;
577
0
  alarmed_time = 0;
578
0
  dynamic_timerno = PR_TIMER_DYNAMIC_TIMERNO;
579
580
  /* Don't inherit the parent's timer lists. */
581
0
  timers = NULL;
582
0
  recycled = NULL;
583
0
  free_timers = NULL;
584
585
  /* Reset the timer pool. */
586
0
  if (timer_pool != NULL) {
587
0
    destroy_pool(timer_pool);
588
0
  }
589
590
0
  timer_pool = make_sub_pool(permanent_pool);
591
0
  pr_pool_tag(timer_pool, "Timer Pool");
592
0
}