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

systemd / systemd / 19520565317

19 Nov 2025 11:19PM UTC coverage: 72.548% (+0.1%) from 72.449%
19520565317

push

github

web-flow
core: Verify inherited FDs are writable for stdout/stderr (#39674)

When inheriting file descriptors for stdout/stderr (either from stdin or
when making stderr inherit from stdout), we previously just assumed they
would be writable and dup'd them. This could lead to broken setups if
the inherited FD was actually opened read-only.

Before dup'ing any inherited FDs to stdout/stderr, verify they are
actually writable using the new fd_is_writable() helper. If not, fall
back to /dev/null (or reopen the terminal in the TTY case) with a
warning, rather than silently creating a broken setup where output
operations would fail.

31 of 44 new or added lines in 3 files covered. (70.45%)

813 existing lines in 43 files now uncovered.

308541 of 425291 relevant lines covered (72.55%)

1188151.68 hits per line

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

96.41
/src/shared/calendarspec.c
1
/* SPDX-License-Identifier: LGPL-2.1-or-later */
2

3
#include <stdio.h>
4
#include <stdlib.h>
5
#include <sys/mman.h>
6
#include <unistd.h>
7

8
#include "alloc-util.h"
9
#include "calendarspec.h"
10
#include "errno-util.h"
11
#include "log.h"
12
#include "memstream-util.h"
13
#include "parse-util.h"
14
#include "process-util.h"
15
#include "sort-util.h"
16
#include "string-util.h"
17
#include "strv.h"
18
#include "time-util.h"
19

20
#define BITS_WEEKDAYS 127
21
#define MIN_YEAR 1970
22
#define MAX_YEAR 2199
23

24
/* An arbitrary limit on the length of the chains of components. We don't want to
25
 * build a very long linked list, which would be slow to iterate over and might cause
26
 * our stack to overflow. It's unlikely that legitimate uses require more than a few
27
 * linked components anyway. */
28
#define CALENDARSPEC_COMPONENTS_MAX 240
29

30
/* Let's make sure that the microsecond component is safe to be stored in an 'int' */
31
assert_cc(INT_MAX >= USEC_PER_SEC);
32

33
static CalendarComponent* chain_free(CalendarComponent *c) {
2,653✔
34
        while (c) {
4,150✔
35
                CalendarComponent *n = c->next;
1,497✔
36
                free_and_replace(c, n);
1,497✔
37
        }
38
        return NULL;
2,653✔
39
}
40

41
DEFINE_TRIVIAL_CLEANUP_FUNC(CalendarComponent*, chain_free);
2,205✔
42

43
CalendarSpec* calendar_spec_free(CalendarSpec *c) {
1,160✔
44

45
        if (!c)
1,160✔
46
                return NULL;
47

48
        chain_free(c->year);
434✔
49
        chain_free(c->month);
434✔
50
        chain_free(c->day);
434✔
51
        chain_free(c->hour);
434✔
52
        chain_free(c->minute);
434✔
53
        chain_free(c->microsecond);
434✔
54
        free(c->timezone);
434✔
55

56
        return mfree(c);
434✔
57
}
58

59
static int component_compare(CalendarComponent * const *a, CalendarComponent * const *b) {
115✔
60
        int r;
115✔
61

62
        r = CMP((*a)->start, (*b)->start);
115✔
63
        if (r != 0)
60✔
64
                return r;
109✔
65

66
        r = CMP((*a)->stop, (*b)->stop);
6✔
67
        if (r != 0)
4✔
68
                return r;
4✔
69

70
        return CMP((*a)->repeat, (*b)->repeat);
2✔
71
}
72

73
static void normalize_chain(CalendarComponent **c) {
2,418✔
74
        assert(c);
2,418✔
75

76
        size_t n = 0;
2,418✔
77
        for (CalendarComponent *i = *c; i; i = i->next) {
3,857✔
78
                n++;
1,439✔
79

80
                /* While we're counting the chain, also normalize 'stop'
81
                 * so the length of the range is a multiple of 'repeat'. */
82
                if (i->stop > i->start && i->repeat > 0)
1,439✔
83
                        i->stop -= (i->stop - i->start) % i->repeat;
35✔
84

85
                /* If a repeat value is specified, but it cannot even be triggered once, let's suppress it.
86
                 *
87
                 * Similarly, if the stop value is the same as the start value, then let's just make this a
88
                 * non-repeating chain element. */
89
                if ((i->stop > i->start && i->repeat > 0 && i->start + i->repeat > i->stop) ||
1,439✔
90
                    i->start == i->stop) {
91
                        i->repeat = 0;
2✔
92
                        i->stop = -1;
2✔
93
                }
94
        }
95

96
        if (n <= 1)
2,418✔
97
                return;
2,382✔
98

99
        CalendarComponent **b, **j;
36✔
100
        b = j = newa(CalendarComponent*, n);
36✔
101
        for (CalendarComponent *i = *c; i; i = i->next)
123✔
102
                *(j++) = i;
87✔
103

104
        typesafe_qsort(b, n, component_compare);
36✔
105

106
        b[n-1]->next = NULL;
36✔
107
        CalendarComponent *next = b[n-1];
36✔
108

109
        /* Drop non-unique entries */
110
        for (size_t k = n-1; k > 0; k--) {
87✔
111
                if (component_compare(&b[k-1], &next) == 0) {
51✔
112
                        free(b[k-1]);
1✔
113
                        continue;
1✔
114
                }
115

116
                b[k-1]->next = next;
50✔
117
                next = b[k-1];
50✔
118
        }
119

120
        *c = next;
36✔
121
}
122

123
static void fix_year(CalendarComponent *c) {
403✔
124
        /* Turns 12 → 2012, 89 → 1989 */
125

126
        while (c) {
460✔
127
                if (c->start >= 0 && c->start < 70)
57✔
128
                        c->start += 2000;
5✔
129

130
                if (c->stop >= 0 && c->stop < 70)
57✔
131
                        c->stop += 2000;
2✔
132

133
                if (c->start >= 70 && c->start < 100)
57✔
134
                        c->start += 1900;
1✔
135

136
                if (c->stop >= 70 && c->stop < 100)
57✔
137
                        c->stop += 1900;
×
138

139
                c = c->next;
57✔
140
        }
141
}
403✔
142

143
static void calendar_spec_normalize(CalendarSpec *c) {
403✔
144
        assert(c);
403✔
145

146
        if (streq_ptr(c->timezone, "UTC")) {
403✔
147
                c->utc = true;
×
148
                c->timezone = mfree(c->timezone);
×
149
        }
150

151
        if (c->weekdays_bits <= 0 || c->weekdays_bits >= BITS_WEEKDAYS)
403✔
152
                c->weekdays_bits = -1;
354✔
153

154
        if (c->end_of_month && !c->day)
403✔
155
                c->end_of_month = false;
1✔
156

157
        fix_year(c->year);
403✔
158

159
        normalize_chain(&c->year);
403✔
160
        normalize_chain(&c->month);
403✔
161
        normalize_chain(&c->day);
403✔
162
        normalize_chain(&c->hour);
403✔
163
        normalize_chain(&c->minute);
403✔
164
        normalize_chain(&c->microsecond);
403✔
165
}
403✔
166

167
static bool chain_valid(CalendarComponent *c, int from, int to, bool end_of_month) {
2,448✔
168
        assert(to >= from);
2,448✔
169

170
        if (!c)
2,448✔
171
                return true;
172

173
        /* Forbid dates more than 28 days from the end of the month */
174
        if (end_of_month)
1,420✔
175
                to -= 3;
16✔
176

177
        if (c->start < from || c->start > to)
1,420✔
178
                return false;
179

180
        /* Avoid overly large values that could cause overflow */
181
        if (c->repeat > to - from)
1,417✔
182
                return false;
183

184
        /*
185
         * c->repeat must be short enough so at least one repetition may
186
         * occur before the end of the interval.  For dates scheduled
187
         * relative to the end of the month, c->start and c->stop
188
         * correspond to the Nth last day of the month.
189
         */
190
        if (c->stop >= 0) {
1,416✔
191
                if (c->stop < from || c ->stop > to)
35✔
192
                        return false;
193

194
                if (c->start + c->repeat > c->stop)
34✔
195
                        return false;
196
        } else {
197
                if (end_of_month && c->start - c->repeat < from)
1,381✔
198
                        return false;
199

200
                if (!end_of_month && c->start + c->repeat > to)
1,370✔
201
                        return false;
202
        }
203

204
        if (c->next)
1,412✔
205
                return chain_valid(c->next, from, to, end_of_month);
50✔
206

207
        return true;
208
}
209

210
_pure_ bool calendar_spec_valid(CalendarSpec *c) {
403✔
211
        assert(c);
403✔
212

213
        if (c->weekdays_bits > BITS_WEEKDAYS)
403✔
214
                return false;
215

216
        if (!chain_valid(c->year, MIN_YEAR, MAX_YEAR, false))
403✔
217
                return false;
218

219
        if (!chain_valid(c->month, 1, 12, false))
403✔
220
                return false;
221

222
        if (!chain_valid(c->day, 1, 31, c->end_of_month))
402✔
223
                return false;
224

225
        if (!chain_valid(c->hour, 0, 23, false))
399✔
226
                return false;
227

228
        if (!chain_valid(c->minute, 0, 59, false))
396✔
229
                return false;
230

231
        if (!chain_valid(c->microsecond, 0, 60*USEC_PER_SEC-1, false))
395✔
232
                return false;
×
233

234
        return true;
235
}
236

237
static void format_weekdays(FILE *f, const CalendarSpec *c) {
39✔
238
        static const char *const days[] = {
39✔
239
                "Mon",
240
                "Tue",
241
                "Wed",
242
                "Thu",
243
                "Fri",
244
                "Sat",
245
                "Sun",
246
        };
247

248
        int l, x;
39✔
249
        bool need_comma = false;
39✔
250

251
        assert(f);
39✔
252
        assert(c);
39✔
253
        assert(c->weekdays_bits > 0 && c->weekdays_bits <= BITS_WEEKDAYS);
39✔
254

255
        for (x = 0, l = -1; x < (int) ELEMENTSOF(days); x++) {
312✔
256

257
                if (c->weekdays_bits & (1 << x)) {
273✔
258

259
                        if (l < 0) {
85✔
260
                                if (need_comma)
49✔
261
                                        fputc(',', f);
10✔
262
                                else
263
                                        need_comma = true;
264

265
                                fputs(days[x], f);
49✔
266
                                l = x;
49✔
267
                        }
268

269
                } else if (l >= 0) {
188✔
270

271
                        if (x > l + 1) {
39✔
272
                                fputs(x > l + 2 ? ".." : ",", f);
8✔
273
                                fputs(days[x-1], f);
8✔
274
                        }
275

276
                        l = -1;
277
                }
278
        }
279

280
        if (l >= 0 && x > l + 1) {
39✔
281
                fputs(x > l + 2 ? ".." : ",", f);
16✔
282
                fputs(days[x-1], f);
8✔
283
        }
284
}
39✔
285

286
static bool chain_is_star(const CalendarComponent *c, bool usec) {
906✔
287
        /* Return true if the whole chain can be replaced by '*'.
288
         * This happens when the chain is empty or one of the components covers all. */
289
        if (!c)
906✔
290
                return true;
291
        if (usec)
551✔
292
                for (; c; c = c->next)
293✔
293
                        if (c->start == 0 && c->stop < 0 && c->repeat == USEC_PER_SEC)
153✔
294
                                return true;
295
        return false;
296
}
297

298
static void _format_chain(FILE *f, int space, const CalendarComponent *c, bool start, bool usec) {
946✔
299
        int d = usec ? (int) USEC_PER_SEC : 1;
946✔
300

301
        assert(f);
946✔
302

303
        if (start && chain_is_star(c, usec)) {
946✔
304
                fputc('*', f);
366✔
305
                return;
366✔
306
        }
307

308
        assert(c->start >= 0);
580✔
309

310
        fprintf(f, "%0*i", space, c->start / d);
580✔
311
        if (c->start % d > 0)
580✔
312
                fprintf(f, ".%06i", c->start % d);
5✔
313

314
        if (c->stop > 0)
580✔
315
                fprintf(f, "..%0*i", space, c->stop / d);
30✔
316
        if (c->stop % d > 0)
580✔
317
                fprintf(f, ".%06i", c->stop % d);
2✔
318

319
        if (c->repeat > 0 && !(c->stop > 0 && c->repeat == d))
580✔
320
                fprintf(f, "/%i", c->repeat / d);
12✔
321
        if (c->repeat % d > 0)
580✔
322
                fprintf(f, ".%06i", c->repeat % d);
2✔
323

324
        if (c->next) {
580✔
325
                fputc(',', f);
40✔
326
                _format_chain(f, space, c->next, false, usec);
40✔
327
        }
328
}
329

330
static void format_chain(FILE *f, int space, const CalendarComponent *c, bool usec) {
906✔
331
        _format_chain(f, space, c, /* start = */ true, usec);
906✔
332
}
906✔
333

334
int calendar_spec_to_string(const CalendarSpec *c, char **ret) {
151✔
335
        _cleanup_(memstream_done) MemStream m = {};
151✔
336
        FILE *f;
151✔
337

338
        assert(c);
151✔
339
        assert(ret);
151✔
340

341
        f = memstream_init(&m);
151✔
342
        if (!f)
151✔
343
                return -ENOMEM;
344

345
        if (c->weekdays_bits > 0 && c->weekdays_bits <= BITS_WEEKDAYS) {
151✔
346
                format_weekdays(f, c);
39✔
347
                fputc(' ', f);
39✔
348
        }
349

350
        format_chain(f, 4, c->year, false);
151✔
351
        fputc('-', f);
151✔
352
        format_chain(f, 2, c->month, false);
151✔
353
        fputc(c->end_of_month ? '~' : '-', f);
296✔
354
        format_chain(f, 2, c->day, false);
151✔
355
        fputc(' ', f);
151✔
356
        format_chain(f, 2, c->hour, false);
151✔
357
        fputc(':', f);
151✔
358
        format_chain(f, 2, c->minute, false);
151✔
359
        fputc(':', f);
151✔
360
        format_chain(f, 2, c->microsecond, true);
151✔
361

362
        if (c->utc)
151✔
363
                fputs(" UTC", f);
13✔
364
        else if (c->timezone) {
138✔
365
                fputc(' ', f);
4✔
366
                fputs(c->timezone, f);
4✔
367
        } else if (IN_SET(c->dst, 0, 1)) {
134✔
368

369
                /* If daylight saving is explicitly on or off, let's show the used timezone. */
370

371
                tzset();
×
372

373
                const char *z = get_tzname(c->dst);
×
374
                if (z) {
×
375
                        fputc(' ', f);
×
376
                        fputs(z, f);
×
377
                }
378
        }
379

380
        return memstream_finalize(&m, ret, NULL);
151✔
381
}
382

383
static int parse_weekdays(const char **p, CalendarSpec *c) {
212✔
384
        static const struct {
212✔
385
                const char *name;
386
                const int nr;
387
        } day_nr[] = {
388
                { "Monday",    0 },
389
                { "Mon",       0 },
390
                { "Tuesday",   1 },
391
                { "Tue",       1 },
392
                { "Wednesday", 2 },
393
                { "Wed",       2 },
394
                { "Thursday",  3 },
395
                { "Thu",       3 },
396
                { "Friday",    4 },
397
                { "Fri",       4 },
398
                { "Saturday",  5 },
399
                { "Sat",       5 },
400
                { "Sunday",    6 },
401
                { "Sun",       6 },
402
        };
403

404
        int l = -1;
212✔
405
        bool first = true;
212✔
406

407
        assert(p);
212✔
408
        assert(*p);
212✔
409
        assert(c);
212✔
410

411
        for (;;) {
252✔
412
                size_t i;
252✔
413

414
                for (i = 0; i < ELEMENTSOF(day_nr); i++) {
3,097✔
415
                        size_t skip;
2,934✔
416

417
                        if (!startswith_no_case(*p, day_nr[i].name))
2,934✔
418
                                continue;
2,845✔
419

420
                        skip = strlen(day_nr[i].name);
89✔
421

422
                        if (!IN_SET((*p)[skip], 0, '-', '.', ',', ' '))
89✔
423
                                return -EINVAL;
424

425
                        c->weekdays_bits |= 1 << day_nr[i].nr;
89✔
426

427
                        if (l >= 0) {
89✔
428
                                if (l > day_nr[i].nr)
12✔
429
                                        return -EINVAL;
430

431
                                for (int j = l + 1; j < day_nr[i].nr; j++)
28✔
432
                                        c->weekdays_bits |= 1 << j;
16✔
433
                        }
434

435
                        *p += skip;
89✔
436
                        break;
89✔
437
                }
438

439
                /* Couldn't find this prefix, so let's assume the
440
                   weekday was not specified and let's continue with
441
                   the date */
442
                if (i >= ELEMENTSOF(day_nr))
252✔
443
                        return first ? 0 : -EINVAL;
163✔
444

445
                /* We reached the end of the string */
446
                if (**p == 0)
89✔
447
                        return 0;
448

449
                /* We reached the end of the weekday spec part */
450
                if (**p == ' ') {
85✔
451
                        *p += strspn(*p, " ");
41✔
452
                        return 0;
41✔
453
                }
454

455
                if (**p == '.') {
44✔
456
                        if (l >= 0)
10✔
457
                                return -EINVAL;
458

459
                        if ((*p)[1] != '.')
10✔
460
                                return -EINVAL;
461

462
                        l = day_nr[i].nr;
10✔
463
                        *p += 2;
10✔
464

465
                /* Support ranges with "-" for backwards compatibility */
466
                } else if (**p == '-') {
34✔
467
                        if (l >= 0)
4✔
468
                                return -EINVAL;
469

470
                        l = day_nr[i].nr;
4✔
471
                        *p += 1;
4✔
472

473
                } else if (**p == ',') {
30✔
474
                        l = -1;
30✔
475
                        *p += 1;
30✔
476
                }
477

478
                /* Allow a trailing comma but not an open range */
479
                if (IN_SET(**p, 0, ' ')) {
44✔
480
                        *p += strspn(*p, " ");
4✔
481
                        return l < 0 ? 0 : -EINVAL;
4✔
482
                }
483

484
                first = false;
485
        }
486
}
487

488
static int parse_one_number(const char *p, const char **e, unsigned long *ret) {
796✔
489
        char *ee = NULL;
796✔
490
        unsigned long value;
796✔
491

492
        errno = 0;
796✔
493
        value = strtoul(p, &ee, 10);
796✔
494
        if (errno > 0)
796✔
495
                return -errno;
×
496
        if (ee == p)
796✔
497
                return -EINVAL;
498

499
        *ret = value;
796✔
500
        *e = ee;
796✔
501
        return 0;
796✔
502
}
503

504
static int parse_component_decimal(const char **p, bool usec, int *res) {
801✔
505
        unsigned long value;
801✔
506
        const char *e = NULL;
801✔
507
        int r;
801✔
508

509
        if (!ascii_isdigit(**p))
801✔
510
                return -EINVAL;
801✔
511

512
        r = parse_one_number(*p, &e, &value);
791✔
513
        if (r < 0)
791✔
514
                return r;
515

516
        if (usec) {
791✔
517
                if (value * USEC_PER_SEC / USEC_PER_SEC != value)
161✔
518
                        return -ERANGE;
519

520
                value *= USEC_PER_SEC;
160✔
521

522
                /* One "." is a decimal point, but ".." is a range separator */
523
                if (e[0] == '.' && e[1] != '.') {
160✔
524
                        unsigned add;
21✔
525

526
                        e++;
21✔
527
                        r = parse_fractional_part_u(&e, 6, &add);
21✔
528
                        if (r < 0)
21✔
529
                                return r;
×
530

531
                        if (add + value < value)
21✔
532
                                return -ERANGE;
533
                        value += add;
21✔
534
                }
535
        }
536

537
        if (value > INT_MAX)
790✔
538
                return -ERANGE;
539

540
        *p = e;
789✔
541
        *res = value;
789✔
542

543
        return 0;
789✔
544
}
545

546
static int const_chain(int value, CalendarComponent **c) {
789✔
547
        CalendarComponent *cc = NULL;
789✔
548

549
        assert(c);
789✔
550

551
        cc = new(CalendarComponent, 1);
789✔
552
        if (!cc)
789✔
553
                return -ENOMEM;
554

555
        *cc = (CalendarComponent) {
789✔
556
                .start = value,
557
                .stop = -1,
558
                .repeat = 0,
559
                .next = *c,
789✔
560
        };
561

562
        *c = cc;
789✔
563

564
        return 0;
789✔
565
}
566

567
static int calendarspec_from_time_t(CalendarSpec *c, time_t time) {
5✔
568
        _cleanup_(chain_freep) CalendarComponent
5✔
569
                *year = NULL, *month = NULL, *day = NULL,
5✔
570
                *hour = NULL, *minute = NULL, *us = NULL;
5✔
571
        struct tm tm;
5✔
572
        int r;
5✔
573

574
        if ((usec_t) time > USEC_INFINITY / USEC_PER_SEC)
5✔
575
                return -ERANGE;
576

577
        r = localtime_or_gmtime_usec((usec_t) time * USEC_PER_SEC, /* utc= */ true, &tm);
4✔
578
        if (r < 0)
4✔
579
                return r;
580

581
        if (tm.tm_year > INT_MAX - 1900)
4✔
582
                return -ERANGE;
583

584
        r = const_chain(tm.tm_year + 1900, &year);
4✔
585
        if (r < 0)
4✔
586
                return r;
587

588
        r = const_chain(tm.tm_mon + 1, &month);
4✔
589
        if (r < 0)
4✔
590
                return r;
591

592
        r = const_chain(tm.tm_mday, &day);
4✔
593
        if (r < 0)
4✔
594
                return r;
595

596
        r = const_chain(tm.tm_hour, &hour);
4✔
597
        if (r < 0)
4✔
598
                return r;
599

600
        r = const_chain(tm.tm_min, &minute);
4✔
601
        if (r < 0)
4✔
602
                return r;
603

604
        r = const_chain(tm.tm_sec * USEC_PER_SEC, &us);
4✔
605
        if (r < 0)
4✔
606
                return r;
607

608
        c->utc = true;
4✔
609
        c->year = TAKE_PTR(year);
4✔
610
        c->month = TAKE_PTR(month);
4✔
611
        c->day = TAKE_PTR(day);
4✔
612
        c->hour = TAKE_PTR(hour);
4✔
613
        c->minute = TAKE_PTR(minute);
4✔
614
        c->microsecond = TAKE_PTR(us);
4✔
615
        return 0;
4✔
616
}
617

618
static int prepend_component(const char **p, bool usec, unsigned nesting, CalendarComponent **c) {
725✔
619
        int r, start, stop = -1, repeat = 0;
725✔
620
        CalendarComponent *cc;
725✔
621
        const char *e = *p;
725✔
622

623
        assert(p);
725✔
624
        assert(c);
725✔
625

626
        if (nesting > CALENDARSPEC_COMPONENTS_MAX)
725✔
627
                return -ENOBUFS;
725✔
628

629
        r = parse_component_decimal(&e, usec, &start);
725✔
630
        if (r < 0)
725✔
631
                return r;
632

633
        if (e[0] == '.' && e[1] == '.') {
713✔
634
                e += 2;
39✔
635
                r = parse_component_decimal(&e, usec, &stop);
39✔
636
                if (r < 0)
39✔
637
                        return r;
638

639
                repeat = usec ? USEC_PER_SEC : 1;
65✔
640
        }
641

642
        if (*e == '/') {
713✔
643
                e++;
37✔
644
                r = parse_component_decimal(&e, usec, &repeat);
37✔
645
                if (r < 0)
37✔
646
                        return r;
647

648
                if (repeat == 0)
37✔
649
                        return -ERANGE;
650
        } else {
651
                /* If no repeat value is specified for the μs component, then let's explicitly refuse ranges
652
                 * below 1s because our default repeat granularity is beyond that. */
653

654
                /* Overflow check */
655
                if (start > INT_MAX - repeat)
676✔
656
                        return -ERANGE;
657

658
                if (usec && stop >= 0 && start + repeat > stop)
676✔
659
                        return -EINVAL;
660
        }
661

662
        if (!IN_SET(*e, 0, ' ', ',', '-', '~', ':'))
711✔
663
                return -EINVAL;
664

665
        cc = new(CalendarComponent, 1);
709✔
666
        if (!cc)
709✔
667
                return -ENOMEM;
668

669
        *cc = (CalendarComponent) {
709✔
670
                .start = start,
671
                .stop = stop,
672
                .repeat = repeat,
673
                .next = *c,
709✔
674
        };
675

676
        *p = e;
709✔
677
        *c = cc;
709✔
678

679
        if (*e ==',') {
709✔
680
                *p += 1;
55✔
681
                return prepend_component(p, usec, nesting + 1, c);
55✔
682
        }
683

684
        return 0;
685
}
686

687
static int parse_chain(const char **p, bool usec, CalendarComponent **c) {
960✔
688
        _cleanup_(chain_freep) CalendarComponent *cc = NULL;
960✔
689
        const char *t;
960✔
690
        int r;
960✔
691

692
        assert(p);
960✔
693
        assert(c);
960✔
694

695
        t = *p;
960✔
696

697
        if (t[0] == '*') {
960✔
698
                if (usec) {
290✔
699
                        r = const_chain(0, c);
12✔
700
                        if (r < 0)
12✔
701
                                return r;
702
                        (*c)->repeat = USEC_PER_SEC;
12✔
703
                } else
704
                        *c = NULL;
278✔
705

706
                *p = t + 1;
290✔
707
                return 0;
290✔
708
        }
709

710
        r = prepend_component(&t, usec, 0, &cc);
670✔
711
        if (r < 0)
670✔
712
                return r;
713

714
        *p = t;
654✔
715
        *c = TAKE_PTR(cc);
654✔
716
        return 0;
654✔
717
}
718

719
static int parse_date(const char **p, CalendarSpec *c) {
210✔
720
        _cleanup_(chain_freep) CalendarComponent *first = NULL, *second = NULL, *third = NULL;
210✔
721
        const char *t;
210✔
722
        int r;
210✔
723

724
        assert(p);
210✔
725
        assert(*p);
210✔
726
        assert(c);
210✔
727

728
        t = *p;
210✔
729

730
        if (*t == 0)
210✔
731
                return 0;
732

733
        /* @TIMESTAMP — UNIX time in seconds since the epoch */
734
        if (*t == '@') {
205✔
735
                unsigned long value;
5✔
736
                time_t time;
5✔
737

738
                r = parse_one_number(t + 1, &t, &value);
5✔
739
                if (r < 0)
5✔
740
                        return r;
5✔
741

742
                time = value;
5✔
743
                if ((unsigned long) time != value)
5✔
744
                        return -ERANGE;
745

746
                r = calendarspec_from_time_t(c, time);
5✔
747
                if (r < 0)
5✔
748
                        return r;
749

750
                *p = t;
4✔
751
                return 1; /* finito, don't parse H:M:S after that */
4✔
752
        }
753

754
        r = parse_chain(&t, false, &first);
200✔
755
        if (r < 0)
200✔
756
                return r;
757

758
        /* Already the end? A ':' as separator? In that case this was a time, not a date */
759
        if (IN_SET(*t, 0, ':'))
193✔
760
                return 0;
761

762
        if (*t == '~')
151✔
763
                c->end_of_month = true;
4✔
764
        else if (*t != '-')
147✔
765
                return -EINVAL;
766

767
        t++;
151✔
768
        r = parse_chain(&t, false, &second);
151✔
769
        if (r < 0)
151✔
770
                return r;
771

772
        /* Got two parts, hence it's month and day */
773
        if (IN_SET(*t, 0, ' ')) {
150✔
774
                *p = t + strspn(t, " ");
18✔
775
                c->month = TAKE_PTR(first);
18✔
776
                c->day = TAKE_PTR(second);
18✔
777
                return 0;
18✔
778
        } else if (c->end_of_month)
132✔
779
                return -EINVAL;
780

781
        if (*t == '~')
131✔
782
                c->end_of_month = true;
10✔
783
        else if (*t != '-')
121✔
784
                return -EINVAL;
785

786
        t++;
131✔
787
        r = parse_chain(&t, false, &third);
131✔
788
        if (r < 0)
131✔
789
                return r;
790

791
        if (!IN_SET(*t, 0, ' '))
130✔
792
                return -EINVAL;
793

794
        /* Got three parts, hence it is year, month and day */
795
        *p = t + strspn(t, " ");
130✔
796
        c->year = TAKE_PTR(first);
130✔
797
        c->month = TAKE_PTR(second);
130✔
798
        c->day = TAKE_PTR(third);
130✔
799
        return 0;
130✔
800
}
801

802
static int parse_calendar_time(const char **p, CalendarSpec *c) {
195✔
803
        _cleanup_(chain_freep) CalendarComponent *h = NULL, *m = NULL, *s = NULL;
195✔
804
        const char *t;
195✔
805
        int r;
195✔
806

807
        assert(p);
195✔
808
        assert(*p);
195✔
809
        assert(c);
195✔
810

811
        t = *p;
195✔
812

813
        /* If no time is specified at all, then this means 00:00:00 */
814
        if (*t == 0)
195✔
815
                goto null_hour;
26✔
816

817
        r = parse_chain(&t, false, &h);
169✔
818
        if (r < 0)
169✔
819
                return r;
820

821
        if (*t != ':')
169✔
822
                return -EINVAL;
823

824
        t++;
166✔
825
        r = parse_chain(&t, false, &m);
166✔
826
        if (r < 0)
166✔
827
                return r;
828

829
        /* Already at the end? Then it's hours and minutes, and seconds are 0 */
830
        if (*t == 0)
164✔
831
                goto null_second;
21✔
832

833
        if (*t != ':')
143✔
834
                return -EINVAL;
835

836
        t++;
143✔
837
        r = parse_chain(&t, true, &s);
143✔
838
        if (r < 0)
143✔
839
                return r;
840

841
        /* At the end? Then it's hours, minutes and seconds */
842
        if (*t == 0)
138✔
843
                goto finish;
136✔
844

845
        return -EINVAL;
846

847
null_hour:
26✔
848
        r = const_chain(0, &h);
26✔
849
        if (r < 0)
26✔
850
                return r;
851

852
        r = const_chain(0, &m);
26✔
853
        if (r < 0)
26✔
854
                return r;
855

856
null_second:
26✔
857
        r = const_chain(0, &s);
47✔
858
        if (r < 0)
47✔
859
                return r;
860

861
finish:
47✔
862
        *p = t;
183✔
863
        c->hour = TAKE_PTR(h);
183✔
864
        c->minute = TAKE_PTR(m);
183✔
865
        c->microsecond = TAKE_PTR(s);
183✔
866

867
        return 0;
183✔
868
}
869

870
int calendar_spec_from_string(const char *p, CalendarSpec **ret) {
434✔
871
        const char *utc;
434✔
872
        _cleanup_(calendar_spec_freep) CalendarSpec *c = NULL;
×
873
        _cleanup_free_ char *p_tmp = NULL;
434✔
874
        int r;
434✔
875

876
        assert(p);
434✔
877

878
        c = new(CalendarSpec, 1);
434✔
879
        if (!c)
434✔
880
                return -ENOMEM;
881

882
        *c = (CalendarSpec) {
434✔
883
                .dst = -1,
884
                .timezone = NULL,
885
        };
886

887
        utc = endswith_no_case(p, " UTC");
434✔
888
        if (utc) {
434✔
889
                c->utc = true;
26✔
890
                p = p_tmp = strndup(p, utc - p);
26✔
891
                if (!p)
26✔
892
                        return -ENOMEM;
893
        } else {
894
                const char *e = NULL;
408✔
895
                int j;
408✔
896

897
                tzset();
408✔
898

899
                /* Check if the local timezone was specified? */
900
                for (j = 0; j <= 1; j++) {
1,632✔
901
                        const char *z = get_tzname(j);
816✔
902
                        if (!z)
816✔
903
                                continue;
×
904

905
                        e = endswith_no_case(p, z);
816✔
906
                        if (!e)
816✔
907
                                continue;
816✔
908
                        if (e == p)
×
909
                                continue;
×
910
                        if (e[-1] != ' ')
×
911
                                continue;
×
912

913
                        break;
914
                }
915

916
                /* Found one of the two timezones specified? */
917
                if (IN_SET(j, 0, 1)) {
408✔
918
                        p = p_tmp = strndup(p, e - p - 1);
×
919
                        if (!p)
×
920
                                return -ENOMEM;
921

922
                        c->dst = j;
×
923
                } else {
924
                        const char *last_space;
408✔
925

926
                        last_space = strrchr(p, ' ');
408✔
927
                        if (last_space != NULL && timezone_is_valid(last_space + 1, LOG_DEBUG)) {
408✔
928
                                c->timezone = strdup(last_space + 1);
13✔
929
                                if (!c->timezone)
13✔
930
                                        return -ENOMEM;
931

932
                                p = p_tmp = strndup(p, last_space - p);
13✔
933
                                if (!p)
13✔
934
                                        return -ENOMEM;
935
                        }
936
                }
937
        }
938

939
        if (isempty(p))
868✔
940
                return -EINVAL;
941

942
        if (strcaseeq(p, "minutely")) {
428✔
943
                r = const_chain(0, &c->microsecond);
1✔
944
                if (r < 0)
1✔
945
                        return r;
946

947
        } else if (strcaseeq(p, "hourly")) {
427✔
948
                r = const_chain(0, &c->minute);
3✔
949
                if (r < 0)
3✔
950
                        return r;
951
                r = const_chain(0, &c->microsecond);
3✔
952
                if (r < 0)
3✔
953
                        return r;
954

955
        } else if (strcaseeq(p, "daily")) {
424✔
956
                r = const_chain(0, &c->hour);
206✔
957
                if (r < 0)
206✔
958
                        return r;
959
                r = const_chain(0, &c->minute);
206✔
960
                if (r < 0)
206✔
961
                        return r;
962
                r = const_chain(0, &c->microsecond);
206✔
963
                if (r < 0)
206✔
964
                        return r;
965

966
        } else if (strcaseeq(p, "monthly")) {
218✔
967
                r = const_chain(1, &c->day);
1✔
968
                if (r < 0)
1✔
969
                        return r;
970
                r = const_chain(0, &c->hour);
1✔
971
                if (r < 0)
1✔
972
                        return r;
973
                r = const_chain(0, &c->minute);
1✔
974
                if (r < 0)
1✔
975
                        return r;
976
                r = const_chain(0, &c->microsecond);
1✔
977
                if (r < 0)
1✔
978
                        return r;
979

980
        } else if (STRCASE_IN_SET(p,
217✔
981
                                  "annually",
982
                                  "yearly",
983
                                  "anually") /* backwards compatibility */ ) {
984

985
                r = const_chain(1, &c->month);
1✔
986
                if (r < 0)
1✔
987
                        return r;
25✔
988
                r = const_chain(1, &c->day);
1✔
989
                if (r < 0)
1✔
990
                        return r;
991
                r = const_chain(0, &c->hour);
1✔
992
                if (r < 0)
1✔
993
                        return r;
994
                r = const_chain(0, &c->minute);
1✔
995
                if (r < 0)
1✔
996
                        return r;
997
                r = const_chain(0, &c->microsecond);
1✔
998
                if (r < 0)
1✔
999
                        return r;
1000

1001
        } else if (strcaseeq(p, "weekly")) {
216✔
1002

1003
                c->weekdays_bits = 1;
2✔
1004

1005
                r = const_chain(0, &c->hour);
2✔
1006
                if (r < 0)
2✔
1007
                        return r;
1008
                r = const_chain(0, &c->minute);
2✔
1009
                if (r < 0)
2✔
1010
                        return r;
1011
                r = const_chain(0, &c->microsecond);
2✔
1012
                if (r < 0)
2✔
1013
                        return r;
1014

1015
        } else if (strcaseeq(p, "quarterly")) {
214✔
1016

1017
                r = const_chain(1, &c->month);
1✔
1018
                if (r < 0)
1✔
1019
                        return r;
1020
                r = const_chain(4, &c->month);
1✔
1021
                if (r < 0)
1✔
1022
                        return r;
1023
                r = const_chain(7, &c->month);
1✔
1024
                if (r < 0)
1✔
1025
                        return r;
1026
                r = const_chain(10, &c->month);
1✔
1027
                if (r < 0)
1✔
1028
                        return r;
1029
                r = const_chain(1, &c->day);
1✔
1030
                if (r < 0)
1✔
1031
                        return r;
1032
                r = const_chain(0, &c->hour);
1✔
1033
                if (r < 0)
1✔
1034
                        return r;
1035
                r = const_chain(0, &c->minute);
1✔
1036
                if (r < 0)
1✔
1037
                        return r;
1038
                r = const_chain(0, &c->microsecond);
1✔
1039
                if (r < 0)
1✔
1040
                        return r;
1041

1042
        } else if (STRCASE_IN_SET(p,
213✔
1043
                                  "biannually",
1044
                                  "bi-annually",
1045
                                  "semiannually",
1046
                                  "semi-annually")) {
1047

1048
                r = const_chain(1, &c->month);
1✔
1049
                if (r < 0)
1✔
1050
                        return r;
25✔
1051
                r = const_chain(7, &c->month);
1✔
1052
                if (r < 0)
1✔
1053
                        return r;
1054
                r = const_chain(1, &c->day);
1✔
1055
                if (r < 0)
1✔
1056
                        return r;
1057
                r = const_chain(0, &c->hour);
1✔
1058
                if (r < 0)
1✔
1059
                        return r;
1060
                r = const_chain(0, &c->minute);
1✔
1061
                if (r < 0)
1✔
1062
                        return r;
1063
                r = const_chain(0, &c->microsecond);
1✔
1064
                if (r < 0)
1✔
1065
                        return r;
1066

1067
        } else {
1068
                r = parse_weekdays(&p, c);
212✔
1069
                if (r < 0)
212✔
1070
                        return r;
1071

1072
                r = parse_date(&p, c);
210✔
1073
                if (r < 0)
210✔
1074
                        return r;
1075

1076
                if (r == 0) {
199✔
1077
                        r = parse_calendar_time(&p, c);
195✔
1078
                        if (r < 0)
195✔
1079
                                return r;
1080
                }
1081

1082
                if (*p != 0)
187✔
1083
                        return -EINVAL;
1084
        }
1085

1086
        calendar_spec_normalize(c);
403✔
1087

1088
        if (!calendar_spec_valid(c))
403✔
1089
                return -EINVAL;
1090

1091
        if (ret)
395✔
1092
                *ret = TAKE_PTR(c);
393✔
1093
        return 0;
1094
}
1095

1096
static int find_end_of_month(const struct tm *tm, bool utc, int day) {
32✔
1097
        struct tm t = *ASSERT_PTR(tm);
32✔
1098

1099
        t.tm_mon++;
32✔
1100
        t.tm_mday = 1 - day;
32✔
1101

1102
        if (mktime_or_timegm_usec(&t, utc, /* ret= */ NULL) < 0 ||
32✔
1103
            t.tm_mon != tm->tm_mon)
32✔
1104
                return -1;
32✔
1105

1106
        return t.tm_mday;
22✔
1107
}
1108

1109
static int find_matching_component(
4,705✔
1110
                const CalendarSpec *spec,
1111
                const CalendarComponent *c,
1112
                const struct tm *tm,           /* tm is only used for end-of-month calculations */
1113
                int *val) {
1114

1115
        int d = -1, r;
4,705✔
1116
        bool d_set = false;
4,705✔
1117

1118
        assert(val);
4,705✔
1119

1120
        /* Finds the *earliest* matching time specified by one of the CalendarCompoment items in chain c.
1121
         * If no matches can be found, returns -ENOENT.
1122
         * Otherwise, updates *val to the matching time. 1 is returned if *val was changed, 0 otherwise.
1123
         */
1124

1125
        if (!c)
4,705✔
1126
                return 0;
1127

1128
        bool end_of_month = spec->end_of_month && c == spec->day;
2,190✔
1129

1130
        while (c) {
4,437✔
1131
                int start, stop;
2,247✔
1132

1133
                if (end_of_month) {
2,247✔
1134
                        start = find_end_of_month(tm, spec->utc, c->start);
16✔
1135
                        stop = find_end_of_month(tm, spec->utc, c->stop);
16✔
1136

1137
                        if (stop > 0)
16✔
1138
                                SWAP_TWO(start, stop);
6✔
1139
                } else {
1140
                        start = c->start;
2,231✔
1141
                        stop = c->stop;
2,231✔
1142
                }
1143

1144
                if (start >= *val) {
2,247✔
1145

1146
                        if (!d_set || start < d) {
1,540✔
1147
                                d = start;
1,512✔
1148
                                d_set = true;
1,512✔
1149
                        }
1150

1151
                } else if (c->repeat > 0) {
707✔
1152
                        int k;
105✔
1153

1154
                        k = start + ROUND_UP(*val - start, c->repeat);
105✔
1155

1156
                        if ((!d_set || k < d) && (stop < 0 || k <= stop)) {
105✔
1157
                                d = k;
100✔
1158
                                d_set = true;
100✔
1159
                        }
1160
                }
1161

1162
                c = c->next;
2,247✔
1163
        }
1164

1165
        if (!d_set)
2,190✔
1166
                return -ENOENT;
1167

1168
        r = *val != d;
1,610✔
1169
        *val = d;
1,610✔
1170
        return r;
1,610✔
1171
}
1172

1173
static int tm_within_bounds(struct tm *tm, bool utc) {
4,125✔
1174
        int r;
4,125✔
1175

1176
        assert(tm);
4,125✔
1177

1178
        /*
1179
         * Set an upper bound on the year so impossible dates like "*-02-31"
1180
         * don't cause find_next() to loop forever. tm_year contains years
1181
         * since 1900, so adjust it accordingly.
1182
         */
1183
        if (tm->tm_year + 1900 > MAX_YEAR)
4,125✔
1184
                return -ERANGE;
4,125✔
1185

1186
        struct tm t = *tm;
4,124✔
1187
        r = mktime_or_timegm_usec(&t, utc, /* ret= */ NULL);
4,124✔
1188
        if (r < 0)
4,124✔
1189
                return r;
1190

1191
        /*
1192
         * Did any normalization take place? If so, it was out of bounds before.
1193
         * Normalization could skip next elapse, e.g. result of normalizing 3-33
1194
         * is 4-2. This skips 4-1. So reset the sub time unit if upper unit was
1195
         * out of bounds. Normalization has occurred implies find_matching_component() > 0,
1196
         * other sub time units are already reset in find_next().
1197
         */
1198
        int cmp;
4,124✔
1199
        if ((cmp = CMP(t.tm_year, tm->tm_year)) != 0)
4,124✔
1200
                t.tm_mon = 0;
4✔
1201
        else if ((cmp = CMP(t.tm_mon, tm->tm_mon)) != 0)
4,120✔
1202
                t.tm_mday = 1;
247✔
1203
        else if ((cmp = CMP(t.tm_mday, tm->tm_mday)) != 0)
3,873✔
1204
                t.tm_hour = 0;
×
1205
        else if ((cmp = CMP(t.tm_hour, tm->tm_hour)) != 0)
3,873✔
1206
                t.tm_min = 0;
4✔
1207
        else if ((cmp = CMP(t.tm_min, tm->tm_min)) != 0)
3,869✔
UNCOV
1208
                t.tm_sec = 0;
×
1209
        else
1210
                cmp = CMP(t.tm_sec, tm->tm_sec);
3,869✔
1211

1212
        if (cmp < 0)
4,124✔
1213
                return -EDEADLK; /* Refuse to go backward */
2✔
1214
        if (cmp > 0)
4,122✔
1215
                *tm = t;
253✔
1216
        return cmp == 0;
4,122✔
1217
}
1218

1219
static bool matches_weekday(int weekdays_bits, const struct tm *tm, bool utc) {
670✔
1220
        struct tm t;
670✔
1221
        int k;
670✔
1222

1223
        if (weekdays_bits < 0 || weekdays_bits >= BITS_WEEKDAYS)
670✔
1224
                return true;
670✔
1225

1226
        t = *tm;
118✔
1227
        if (mktime_or_timegm_usec(&t, utc, /* ret= */ NULL) < 0)
118✔
1228
                return false;
1229

1230
        k = t.tm_wday == 0 ? 6 : t.tm_wday - 1;
118✔
1231
        return (weekdays_bits & (1 << k));
118✔
1232
}
1233

1234
static int tm_compare(const struct tm *t1, const struct tm *t2) {
309✔
1235
        int r;
309✔
1236

1237
        assert(t1);
309✔
1238
        assert(t2);
309✔
1239

1240
        r = CMP(t1->tm_year, t2->tm_year);
309✔
1241
        if (r != 0)
271✔
1242
                return r;
38✔
1243

1244
        r = CMP(t1->tm_mon, t2->tm_mon);
271✔
1245
        if (r != 0)
264✔
1246
                return r;
7✔
1247

1248
        r = CMP(t1->tm_mday, t2->tm_mday);
264✔
1249
        if (r != 0)
92✔
1250
                return r;
172✔
1251

1252
        r = CMP(t1->tm_hour, t2->tm_hour);
92✔
1253
        if (r != 0)
73✔
1254
                return r;
19✔
1255

1256
        r = CMP(t1->tm_min, t2->tm_min);
73✔
1257
        if (r != 0)
69✔
1258
                return r;
4✔
1259

1260
        return CMP(t1->tm_sec, t2->tm_sec);
69✔
1261
}
1262

1263
/* A safety valve: if we get stuck in the calculation, return an error.
1264
 * C.f. https://bugzilla.redhat.com/show_bug.cgi?id=1941335. */
1265
#define MAX_CALENDAR_ITERATIONS 1000
1266

1267
static int find_next(const CalendarSpec *spec, struct tm *tm, usec_t *usec) {
327✔
1268
        struct tm c;
327✔
1269
        int tm_usec, r;
327✔
1270
        bool invalidate_dst = false;
327✔
1271

1272
        /* Returns -ENOENT if the expression is not going to elapse anymore */
1273

1274
        assert(spec);
327✔
1275
        assert(tm);
327✔
1276

1277
        c = *tm;
327✔
1278
        tm_usec = *usec;
327✔
1279

1280
        for (unsigned iteration = 0; iteration < MAX_CALENDAR_ITERATIONS; iteration++) {
1,234✔
1281
                /* Normalize the current date */
1282
                (void) mktime_or_timegm_usec(&c, spec->utc, /* ret= */ NULL);
1,234✔
1283
                if (!invalidate_dst)
1,234✔
1284
                        c.tm_isdst = spec->dst;
1,232✔
1285

1286
                c.tm_year += 1900;
1,234✔
1287
                r = find_matching_component(spec, spec->year, &c, &c.tm_year);
1,234✔
1288
                c.tm_year -= 1900;
1,234✔
1289

1290
                if (r > 0) {
1,234✔
1291
                        c.tm_mon = 0;
19✔
1292
                        c.tm_mday = 1;
19✔
1293
                        c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0;
19✔
1294
                }
1295
                if (r < 0)
1,234✔
1296
                        return r;
327✔
1297
                if (tm_within_bounds(&c, spec->utc) <= 0)
1,215✔
1298
                        return -ENOENT;
1299

1300
                c.tm_mon += 1;
1,214✔
1301
                r = find_matching_component(spec, spec->month, &c, &c.tm_mon);
1,214✔
1302
                c.tm_mon -= 1;
1,214✔
1303

1304
                if (r > 0) {
1,214✔
1305
                        c.tm_mday = 1;
287✔
1306
                        c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0;
287✔
1307
                }
1308
                if (r < 0 || (r = tm_within_bounds(&c, spec->utc)) < 0) {
1,214✔
1309
                        c.tm_year++;
262✔
1310
                        c.tm_mon = 0;
262✔
1311
                        c.tm_mday = 1;
262✔
1312
                        c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0;
262✔
1313
                        continue;
262✔
1314
                }
1315
                if (r == 0)
952✔
1316
                        continue;
4✔
1317

1318
                r = find_matching_component(spec, spec->day, &c, &c.tm_mday);
948✔
1319
                if (r > 0)
948✔
1320
                        c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0;
287✔
1321
                if (r < 0 || (r = tm_within_bounds(&c, spec->utc)) < 0) {
948✔
1322
                        c.tm_mon++;
31✔
1323
                        c.tm_mday = 1;
31✔
1324
                        c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0;
31✔
1325
                        continue;
31✔
1326
                }
1327
                if (r == 0)
917✔
1328
                        continue;
247✔
1329

1330
                if (!matches_weekday(spec->weekdays_bits, &c, spec->utc)) {
670✔
1331
                        c.tm_mday++;
89✔
1332
                        c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0;
89✔
1333
                        continue;
89✔
1334
                }
1335

1336
                r = find_matching_component(spec, spec->hour, &c, &c.tm_hour);
581✔
1337
                if (r > 0)
581✔
1338
                        c.tm_min = c.tm_sec = tm_usec = 0;
37✔
1339
                if (r < 0 || (r = tm_within_bounds(&c, spec->utc)) < 0) {
581✔
1340
                        c.tm_mday++;
171✔
1341
                        c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0;
171✔
1342
                        continue;
171✔
1343
                }
1344
                if (r == 0)
410✔
1345
                        /* The next hour we set might be missing if there
1346
                         * are time zone changes. Let's try again starting at
1347
                         * normalized time. */
1348
                        continue;
2✔
1349

1350
                r = find_matching_component(spec, spec->minute, &c, &c.tm_min);
408✔
1351
                if (r > 0)
408✔
1352
                        c.tm_sec = tm_usec = 0;
35✔
1353
                if (r < 0 || (r = tm_within_bounds(&c, spec->utc)) < 0) {
408✔
1354
                        c.tm_hour++;
88✔
1355
                        c.tm_min = c.tm_sec = tm_usec = 0;
88✔
1356
                        continue;
88✔
1357
                }
1358
                if (r == 0)
320✔
1359
                        continue;
×
1360

1361
                c.tm_sec = c.tm_sec * USEC_PER_SEC + tm_usec;
320✔
1362
                r = find_matching_component(spec, spec->microsecond, &c, &c.tm_sec);
320✔
1363
                tm_usec = c.tm_sec % USEC_PER_SEC;
320✔
1364
                c.tm_sec /= USEC_PER_SEC;
320✔
1365

1366
                if (r < 0 || (r = tm_within_bounds(&c, spec->utc)) < 0) {
320✔
1367
                        c.tm_min++;
11✔
1368
                        c.tm_sec = tm_usec = 0;
11✔
1369
                        continue;
11✔
1370
                }
1371
                if (r == 0)
309✔
UNCOV
1372
                        continue;
×
1373

1374
                r = tm_compare(tm, &c);
309✔
1375
                if (r == 0) {
309✔
1376
                        assert(tm_usec + 1 <= 1000000);
3✔
1377
                        r = CMP(*usec, (usec_t) tm_usec + 1);
3✔
1378
                }
1379
                if (r >= 0) {
306✔
1380
                        /* We're stuck - advance, let mktime determine DST transition and try again. */
1381
                        invalidate_dst = true;
2✔
1382
                        c.tm_hour++;
2✔
1383
                        continue;
2✔
1384
                }
1385

1386
                *tm = c;
307✔
1387
                *usec = tm_usec;
307✔
1388
                return 0;
307✔
1389
        }
1390

1391
        /* It seems we entered an infinite loop. Let's gracefully return an error instead of hanging or
1392
         * aborting. This code is also exercised when timers.target is brought up during early boot, so
1393
         * aborting here is problematic and hard to diagnose for users. */
1394
        _cleanup_free_ char *s = NULL;
×
1395
        (void) calendar_spec_to_string(spec, &s);
×
1396
        return log_warning_errno(SYNTHETIC_ERRNO(EDEADLK),
×
1397
                                 "Infinite loop in calendar calculation: %s", strna(s));
1398
}
1399

1400
static int calendar_spec_next_usec_impl(const CalendarSpec *spec, usec_t usec, usec_t *ret_next) {
327✔
1401
        usec_t tm_usec;
327✔
1402
        struct tm tm;
327✔
1403
        int r;
327✔
1404

1405
        assert(spec);
327✔
1406

1407
        if (usec > USEC_TIMESTAMP_FORMATTABLE_MAX)
327✔
1408
                return -EINVAL;
327✔
1409

1410
        usec++;
327✔
1411
        r = localtime_or_gmtime_usec(usec, spec->utc, &tm);
327✔
1412
        if (r < 0)
327✔
1413
                return r;
1414
        tm_usec = usec % USEC_PER_SEC;
327✔
1415

1416
        r = find_next(spec, &tm, &tm_usec);
327✔
1417
        if (r < 0)
327✔
1418
                return r;
1419

1420
        usec_t t;
307✔
1421
        r = mktime_or_timegm_usec(&tm, spec->utc, &t);
307✔
1422
        if (r < 0)
307✔
1423
                return r;
1424

1425
        if (ret_next)
307✔
1426
                *ret_next = t + tm_usec;
305✔
1427

1428
        return 0;
1429
}
1430

1431
int calendar_spec_next_usec(const CalendarSpec *spec, usec_t usec, usec_t *ret_next) {
327✔
1432
        int r;
327✔
1433

1434
        assert(spec);
327✔
1435

1436
        if (isempty(spec->timezone))
327✔
1437
                return calendar_spec_next_usec_impl(spec, usec, ret_next);
327✔
1438

1439
        SAVE_TIMEZONE;
22✔
1440

1441
        r = RET_NERRNO(setenv("TZ", spec->timezone, /* overwrite = */ true));
11✔
1442
        if (r < 0)
×
1443
                return r;
1444

1445
        tzset();
11✔
1446

1447
        return calendar_spec_next_usec_impl(spec, usec, ret_next);
11✔
1448
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc