• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

proftpd / proftpd / 30135626854

25 Jul 2026 12:15AM UTC coverage: 93.034% (+0.6%) from 92.428%
30135626854

push

github

51363 of 55209 relevant lines covered (93.03%)

219.82 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

91.9
/src/timers.c
1
/*
2
 * ProFTPD - FTP server daemon
3
 * Copyright (c) 1997, 1998 Public Flood Software
4
 * Copyright (c) 2001-2026 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, see <https://www.gnu.org/licenses/>.
18
 *
19
 * As a special exemption, Public Flood Software/MacGyver aka Habeeb J. Dihu
20
 * and other respective copyright holders give permission to link this program
21
 * with OpenSSL, and distribute the resulting executable, without including
22
 * the source code for OpenSSL in the source distribution.
23
 */
24

25
/* Timer system, based on alarm() and SIGALRM. */
26

27
#include "conf.h"
28

29
/* From src/main.c */
30
extern volatile unsigned int recvd_signal_flags;
31

32
struct timer {
33
  struct timer *next, *prev;
34

35
  long count;                   /* Amount of time remaining */
36
  long interval;                /* Original length of timer */
37

38
  int timerno;                  /* Caller dependent timer number */
39
  module *mod;                  /* Module owning this timer */
40
  callback_t callback;          /* Function to callback */
41
  char remove;                  /* Internal use */
42

43
  const char *desc;                /* Description of timer, provided by caller */
44
};
45

46
#define PR_TIMER_DYNAMIC_TIMERNO        1024
47

48
static int current_timeout = 0;
49
static int total_time = 0;
50
static int sleep_sem = 0;
51
static int alarms_blocked = 0, alarm_pending = 0;
52
static xaset_t *timers = NULL;
53
static xaset_t *recycled = NULL;
54
static xaset_t *free_timers = NULL;
55
static int _indispatch = 0;
56
static int dynamic_timerno = PR_TIMER_DYNAMIC_TIMERNO;
57
static unsigned int nalarms = 0;
58
static time_t alarmed_time = 0;
59

60
static pool *timer_pool = NULL;
61

62
static const char *trace_channel = "timer";
63

64
static int timer_cmp(struct timer *t1, struct timer *t2) {
65
  if (t1->count < t2->count) {
2✔
66
    return -1;
2✔
67
  }
68

69
  if (t1->count > t2->count) {
70
    return 1;
2✔
71
  }
×
72

73
  return 0;
74
}
75

76
static void timer_cleanup(void *user_data) {
77
  timers = NULL;
78
  free_timers = NULL;
79
}
80

81
/* This function does the work of iterating through the list of registered
82
 * timers, checking to see if their callbacks should be invoked and whether
34✔
83
 * they should be removed from the registration list. Its return value is
34✔
84
 * the amount of time remaining on the first timer in the list.
34✔
85
 */
86
static int process_timers(int elapsed) {
34✔
87
  struct timer *t = NULL, *next = NULL;
8✔
88
  int res = 0;
89

90
  if (recycled == NULL) {
34✔
91
    recycled = xaset_create(timer_pool, NULL);
28✔
92
  }
28✔
93

94
  if (elapsed == 0 &&
95
      recycled->xas_list == NULL) {
96
    if (timers == NULL) {
28✔
97
      return 0;
98
    }
99

100
    if (timers->xas_list != NULL) {
101
      /* The value we return is a proposed timeout, for the next call to
102
       * alarm(3).  We start with the simple count of timers in our list.
103
       *
17✔
104
       * But then we reduce the number; some of the timers' intervals may
17✔
105
       * less than the number of total timers.
106
       */
107
      res = ((struct timer *) timers->xas_list)->count;
108
      if (res > 5) {
109
        res = 5;
28✔
110
      }
111
    }
112

113
    return res;
6✔
114
  }
115

116
  /* Critical code, no interruptions please */
117
  if (_indispatch) {
6✔
118
    return 0;
6✔
119
  }
120

6✔
121
  pr_alarms_block();
11✔
122
  _indispatch++;
123

5✔
124
  if (elapsed) {
125
    for (t = (struct timer *) timers->xas_list; t; t = next) {
5✔
126
      /* If this timer has already been handled, skip */
127
      next = t->next;
×
128

×
129
      if (t->remove) {
130
        /* Move the timer onto the free_timers chain, for later reuse. */
5✔
131
        xaset_remove(timers, (xasetmember_t *) t);
132
        xaset_insert(free_timers, (xasetmember_t *) t);
133

5✔
134
      } else if ((t->count -= elapsed) <= 0) {
135
        /* This timer's interval has elapsed, so trigger its callback. */
136

5✔
137
        pr_trace_msg(trace_channel, 4,
5✔
138
          "%ld %s for timer ID %d ('%s', for module '%s') elapsed, invoking "
5✔
139
          "callback (%p)", t->interval,
140
          t->interval != 1 ? "seconds" : "second", t->timerno,
5✔
141
          t->desc ? t->desc : "<unknown>",
5✔
142
          t->mod ? t->mod->name : "<none>", t->callback);
143

144
        if (t->callback(t->interval, t->timerno, t->interval - t->count,
145
            t->mod) == 0) {
146

3✔
147
          /* A return value of zero means this timer is done, and can be
3✔
148
           * removed.
149
           */
150
          xaset_remove(timers, (xasetmember_t *) t);
151
          xaset_insert(free_timers, (xasetmember_t *) t);
152

153
        } else {
2✔
154
          /* A non-zero return value from a timer callback signals that
155
           * the timer should be reused/restarted.
2✔
156
           */
157
          pr_trace_msg(trace_channel, 6,
2✔
158
            "restarting timer ID %d ('%s'), as per callback", t->timerno,
2✔
159
            t->desc ? t->desc : "<unknown>");
2✔
160

161
          xaset_remove(timers, (xasetmember_t *) t);
162
          t->count = t->interval;
163
          xaset_insert(recycled, (xasetmember_t *) t);
164
        }
165
      }
166
    }
6✔
167
  }
9✔
168

3✔
169
  /* Put the recycled timers back into the main timer list. */
170
  t = (struct timer *) recycled->xas_list;
3✔
171
  while (t != NULL) {
3✔
172
    pr_signals_handle();
3✔
173

174
    xaset_remove(recycled, (xasetmember_t *) t);
175
    xaset_insert_sort(timers, (xasetmember_t *) t, TRUE);
6✔
176
    t = (struct timer *) recycled->xas_list;
6✔
177
  }
178

179
  _indispatch--;
180
  pr_alarms_unblock();
181

182
  /* If no active timers remain in the list, there is no reason to set the
6✔
183
   * SIGALRM handle.
184
   */
185

186
  if (timers->xas_list != NULL) {
187
    /* The value we return is a proposed timeout, for the next call to
188
     * alarm(3).  We start with the simple count of timers in our list.
189
     *
3✔
190
     * But then we reduce the number; some of the timers' intervals may
3✔
191
     * less than the number of total timers.
192
     */
193
    res = ((struct timer *) timers->xas_list)->count;
194
    if (res > 5) {
195
      res = 5;
196
    }
197
  }
198

5✔
199
  return res;
5✔
200
}
201

5✔
202
static time_t get_current_time(void) {
5✔
203
  time_t now;
5✔
204
  int use_fallback = TRUE;
205

206
#if defined(HAVE_CLOCK_GETTIME) && \
5✔
207
    defined(HAVE_CLOCK_MONOTONIC)
208
  struct timespec ts;
209

210
  if (clock_gettime(CLOCK_MONOTONIC, &ts) < 0) {
5✔
211
    pr_trace_msg(trace_channel, 1,
×
212
      "clock_gettime(3) error: %s, falling back to time(3)", strerror(errno));
213

×
214
  } else {
215
    now = ts.tv_sec;
216
    use_fallback = FALSE;
217
  }
5✔
218
#endif /* HAVE_CLOCK_GETTIME and HAVE_CLOCK_MONOTONIC */
×
219

×
220
  if (use_fallback == TRUE) {
221
    time(&now);
222
  }
223

5✔
224
  return now;
5✔
225
}
226

227
static RETSIGTYPE sig_alarm(int signo) {
5✔
228
#if defined(HAVE_SIGACTION)
5✔
229
  struct sigaction act;
5✔
230

5✔
231
  act.sa_handler = sig_alarm;
232
  sigemptyset(&act.sa_mask);
5✔
233
  act.sa_flags = 0;
234

17✔
235
# if defined(SA_INTERRUPT)
17✔
236
  act.sa_flags |= SA_INTERRUPT;
237
#endif /* SA_INTERRUPT */
17✔
238
# if defined(SA_RESTART)
17✔
239
  act.sa_flags &= SA_RESTART;
17✔
240
#endif /* SA_RESTART */
241

17✔
242
  /* Install this handler for SIGALRM. */
243
  if (sigaction(SIGALRM, &act, NULL) < 0) {
244
    pr_log_pri(PR_LOG_WARNING,
245
      "unable to install SIGALRM handler via sigaction(2): %s",
17✔
246
      strerror(errno));
×
247
  }
248

×
249
#elif defined(HAVE_SIGINTERRUPT)
250
  if (siginterrupt(SIGALRM, 1) < 0) {
251
    pr_log_pri(PR_LOG_WARNING,
252
      "unable to allow SIGALRM to interrupt system calls: %s", strerror(errno));
17✔
253
  }
×
254
#endif /* HAVE_SIGACTION or HAVE_SIGINTERRUPT */
×
255

256
  recvd_signal_flags |= RECEIVED_SIG_ALRM;
257
  nalarms++;
17✔
258

259
  /* Reset the alarm */
65✔
260
  total_time += current_timeout;
261
  if (current_timeout) {
262
    alarmed_time = get_current_time();
263
    alarm(current_timeout);
264
  }
265
}
266

267
static void set_sig_alarm(void) {
268
#if defined(HAVE_SIGACTION)
269
  struct sigaction act;
270

271
  act.sa_handler = sig_alarm;
130✔
272
  sigemptyset(&act.sa_mask);
65✔
273
  act.sa_flags = 0;
274

65✔
275
# if defined(SA_INTERRUPT)
34✔
276
  act.sa_flags |= SA_INTERRUPT;
34✔
277
# endif /* SA_INTERRUPT */
278
# if defined(SA_RESTART)
279
  act.sa_flags &= SA_RESTART;
34✔
280
# endif /* SA_RESTART */
281

282
  /* Install this handler for SIGALRM. */
34✔
283
  if (sigaction(SIGALRM, &act, NULL) < 0) {
34✔
284
    pr_log_pri(PR_LOG_WARNING,
285
      "unable to install SIGALRM handler via sigaction(2): %s",
34✔
286
      strerror(errno));
34✔
287
  }
34✔
288

289
#elif defined(HAVE_SIGINTERRUPT)
34✔
290
  if (siginterrupt(SIGALRM, 1) < 0) {
34✔
291
    pr_log_pri(PR_LOG_WARNING,
292
      "unable to allow SIGALRM to interrupt system calls: %s", strerror(errno));
293
  }
31✔
294
#endif /* HAVE_SIGACTION or HAVE_SIGINTERRUPT */
295
}
296

297
void handle_alarm(void) {
65✔
298
  /* We need to adjust for any time that might be remaining on the alarm,
65✔
299
   * in case we were called in order to change alarm durations.  Note
300
   * that rapid-fire calling of this function will probably screw
64✔
301
   * up the already poor resolution of alarm() _horribly_.  Oh well,
64✔
302
   * this shouldn't be used for any precise work anyway, it's only
303
   * for modules to perform approximate timing.
64✔
304
   */
53✔
305

53✔
306
  /* It's possible that alarms are blocked when this function is
307
   * called, if so, increment alarm_pending and exit swiftly.
308
   */
11✔
309
  while (nalarms) {
×
310
    nalarms = 0;
×
311

312
    if (!alarms_blocked) {
313
      int alarm_elapsed, new_timeout;
11✔
314
      time_t now;
315

11✔
316
      /* Clear any pending ALRM signals. */
×
317
      alarm(0);
318

319
      /* Determine how much time has elapsed since we last processed timers. */
21✔
320
      now = get_current_time();
11✔
321
      alarm_elapsed = alarmed_time > 0 ? (int) (now - alarmed_time) : 0;
1✔
322

1✔
323
      new_timeout = total_time + alarm_elapsed;
1✔
324
      total_time = 0;
1✔
325
      new_timeout = process_timers(new_timeout);
1✔
326

327
      alarmed_time = now;
328
      alarm(current_timeout = new_timeout);
329

330
    } else {
331
      alarm_pending++;
1✔
332
    }
1✔
333
  }
334

335
  pr_signals_handle();
336
}
11✔
337

338
int pr_timer_reset(int timerno, module *mod) {
11✔
339
  struct timer *t = NULL;
1✔
340

1✔
341
  if (timers == NULL) {
1✔
342
    errno = EPERM;
343
    return -1;
344
  }
345

346
  if (_indispatch) {
347
    errno = EINTR;
31✔
348
    return -1;
31✔
349
  }
31✔
350

351
  pr_alarms_block();
352

31✔
353
  if (recycled == NULL) {
354
    recycled = xaset_create(timer_pool, NULL);
355
  }
356

12✔
357
  for (t = (struct timer *) timers->xas_list; t; t = t->next) {
358
    if (t->timerno == timerno &&
16✔
359
        (t->mod == mod || mod == ANY_MODULE)) {
14✔
360
      t->count = t->interval;
361
      xaset_remove(timers, (xasetmember_t *) t);
14✔
362
      xaset_insert(recycled, (xasetmember_t *) t);
4✔
363
      nalarms++;
13✔
364

365
      /* The handle_alarm() function also readjusts the timers lists
13✔
366
       * as part of its processing, so it needs to be called when a timer
×
367
       * is reset.
368
       */
369
      handle_alarm();
13✔
370
      break;
13✔
371
    }
13✔
372
  }
373

374
  pr_alarms_unblock();
375

376
  if (t != NULL) {
377
    pr_trace_msg(trace_channel, 7, "reset timer ID %d ('%s', for module '%s')",
13✔
378
      t->timerno, t->desc, t->mod ? t->mod->name : "[none]");
379
    return t->timerno;
380
  }
13✔
381

382
  return 0;
13✔
383
}
384

385
int pr_timer_remove(int timerno, module *mod) {
386
  struct timer *t = NULL, *tnext = NULL;
387
  int nremoved = 0;
388

14✔
389
  /* If there are no timers currently registered, do nothing. */
14✔
390
  if (timers == NULL) {
391
    return 0;
392
  }
393

394
  pr_alarms_block();
12✔
395

396
  for (t = (struct timer *) timers->xas_list; t; t = tnext) {
12✔
397
    tnext = t->next;
1✔
398

1✔
399
    if ((timerno < 0 || t->timerno == timerno) &&
400
        (mod == ANY_MODULE || t->mod == mod)) {
401
      nremoved++;
402

403
      if (_indispatch) {
404
        t->remove++;
11✔
405

10✔
406
      } else {
407
        xaset_remove(timers, (xasetmember_t *) t);
408
        xaset_insert(free_timers, (xasetmember_t *) t);
409
        nalarms++;
410

411
        /* The handle_alarm() function also readjusts the timers lists
23✔
412
         * as part of its processing, so it needs to be called when a timer
413
         * is removed.
23✔
414
         */
415
        handle_alarm();
23✔
416
      }
23✔
417

418
      pr_trace_msg(trace_channel, 7,
5✔
419
        "removed timer ID %d ('%s', for module '%s')", t->timerno, t->desc,
5✔
420
        t->mod ? t->mod->name : "[none]");
421
    }
422

18✔
423
    /* If we are removing a specific timer, break out of the loop now.
8✔
424
     * Otherwise, keep removing any matching timers.
425
     */
426
    if (nremoved > 0 &&
427
        timerno >= 0) {
18✔
428
      break;
13✔
429
    }
1✔
430
  }
1✔
431

1✔
432
  pr_alarms_unblock();
433

434
  if (nremoved == 0) {
435
    errno = ENOENT;
436
    return -1;
17✔
437
  }
8✔
438

439
  /* If we removed a specific timer because of the given timerno, return
440
   * that timerno value.
441
   */
17✔
442
  if (timerno >= 0) {
17✔
443
    return timerno;
17✔
444
  }
7✔
445

446
  return nremoved;
447
}
10✔
448

3✔
449
int pr_timer_add(int seconds, int timerno, module *mod, callback_t cb,
3✔
450
    const char *desc) {
451
  struct timer *t = NULL;
452

453
  if (seconds <= 0 ||
10✔
454
      cb == NULL ||
455
      desc == NULL) {
456
    errno = EINVAL;
17✔
457
    return -1;
458
  }
5✔
459

×
460
  if (timers == NULL) {
461
    timers = xaset_create(timer_pool, (XASET_COMPARE) timer_cmp);
462
  }
5✔
463

464
  /* Check to see that, if specified, the timerno is not already in use. */
465
  if (timerno >= 0) {
17✔
466
    for (t = (struct timer *) timers->xas_list; t; t = t->next) {
17✔
467
      if (t->timerno == timerno) {
17✔
468
        errno = EPERM;
17✔
469
        return -1;
17✔
470
      }
17✔
471
    }
472
  }
473

474
  if (free_timers == NULL) {
475
    free_timers = xaset_create(timer_pool, NULL);
476
  }
17✔
477

×
478
  /* Try to use an old timer first */
×
479
  pr_alarms_block();
480
  t = (struct timer *) free_timers->xas_list;
×
481
  if (t != NULL) {
482
    xaset_remove(free_timers, (xasetmember_t *) t);
483

17✔
484
  } else {
17✔
485
    if (timer_pool == NULL) {
17✔
486
      timer_pool = make_sub_pool(permanent_pool);
487
      pr_pool_tag(timer_pool, "Timer Pool");
488
      register_cleanup2(timer_pool, NULL, timer_cleanup);
489
    }
490

491
    /* Must allocate a new one */
17✔
492
    t = palloc(timer_pool, sizeof(struct timer));
493
  }
494

17✔
495
  if (timerno < 0) {
496
    /* Dynamic timer */
3✔
497
    if (dynamic_timerno < PR_TIMER_DYNAMIC_TIMERNO) {
498
      dynamic_timerno = PR_TIMER_DYNAMIC_TIMERNO;
17✔
499
    }
17✔
500

17✔
501
    timerno = dynamic_timerno++;
502
  }
503

504
  t->timerno = timerno;
505
  t->count = t->interval = seconds;
506
  t->callback = cb;
507
  t->mod = mod;
508
  t->remove = 0;
509
  t->desc = desc;
43,657✔
510

46✔
511
  /* If called while _indispatch, add to the recycled list to prevent
43,611✔
512
   * list corruption
513
   */
43,657✔
514

43,657✔
515
  if (_indispatch) {
43,657✔
516
    if (recycled == NULL) {
29✔
517
      recycled = xaset_create(timer_pool, NULL);
29✔
518
    }
29✔
519
    xaset_insert(recycled, (xasetmember_t *) t);
520

43,657✔
521
  } else {
522
    xaset_insert_sort(timers, (xasetmember_t *) t, TRUE);
1✔
523
    nalarms++;
1✔
524
    set_sig_alarm();
1✔
525

526
    /* The handle_alarm() function also readjusts the timers lists
527
     * as part of its processing, so it needs to be called when a timer
3✔
528
     * is added.
3✔
529
     */
3✔
530
    handle_alarm();
531
  }
3✔
532

533
  pr_alarms_unblock();
3✔
534

535
  pr_trace_msg(trace_channel, 7, "added timer ID %d ('%s', for module '%s'), "
1✔
536
    "triggering in %ld %s", t->timerno, t->desc,
1✔
537
    t->mod ? t->mod->name : "[none]", t->interval,
538
    t->interval != 1 ? "seconds" : "second");
539
  return timerno;
2✔
540
}
2✔
541

542
/* Alarm blocking.  This is done manually rather than with syscalls,
543
 * so as to allow for easier signal handling, portability and
544
 * detecting the number of blocked alarms, as well as nesting the
1✔
545
 * block/unblock functions.
2✔
546
 */
1✔
547

1✔
548
void pr_alarms_block(void) {
549
  ++alarms_blocked;
550
}
551

552
void pr_alarms_unblock(void) {
553
  --alarms_blocked;
2✔
554
  if (alarms_blocked == 0 && alarm_pending) {
2✔
555
    alarm_pending = 0;
556
    nalarms++;
2✔
557
    handle_alarm();
1✔
558
  }
1✔
559
}
560

561
static int sleep_cb(CALLBACK_FRAME) {
1✔
562
  sleep_sem++;
1✔
563
  return 0;
564
}
1✔
565

1✔
566
int pr_timer_sleep(int seconds) {
1✔
567
  int timerno = 0;
568
  sigset_t oset;
1✔
569

570
  sleep_sem = 0;
571

6✔
572
  if (alarms_blocked ||
573
      _indispatch) {
574
    errno = EPERM;
6✔
575
    return -1;
6✔
576
  }
6✔
577

6✔
578
  timerno = pr_timer_add(seconds, -1, NULL, sleep_cb, "sleep");
6✔
579
  if (timerno == -1) {
580
    return -1;
581
  }
6✔
582

6✔
583
  sigemptyset(&oset);
6✔
584
  while (!sleep_sem) {
585
    sigsuspend(&oset);
586
    handle_alarm();
6✔
587
  }
×
588

589
  return 0;
590
}
6✔
591

6✔
592
int pr_timer_usleep(unsigned long usecs) {
6✔
593
  struct timeval tv;
594

595
  if (usecs == 0) {
596
    errno = EINVAL;
597
    return -1;
598
  }
599

600
  tv.tv_sec = (usecs / 1000000);
601
  tv.tv_usec = (usecs - (tv.tv_sec * 1000000));
602

603
  pr_signals_block();
604
  (void) select(0, NULL, NULL, NULL, &tv);
605
  pr_signals_unblock();
606

607
  return 0;
608
}
609

610
void timers_init(void) {
611

612
  /* Reset some of the key static variables. */
613
  current_timeout = 0;
614
  total_time = 0;
615
  nalarms = 0;
616
  alarmed_time = 0;
617
  dynamic_timerno = PR_TIMER_DYNAMIC_TIMERNO;
618

619
  /* Don't inherit the parent's timer lists. */
620
  timers = NULL;
621
  recycled = NULL;
622
  free_timers = NULL;
623

624
  /* Reset the timer pool. */
625
  if (timer_pool != NULL) {
626
    destroy_pool(timer_pool);
627
  }
628

629
  timer_pool = make_sub_pool(permanent_pool);
630
  pr_pool_tag(timer_pool, "Timer Pool");
631
  register_cleanup2(timer_pool, NULL, timer_cleanup);
632
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc