varnish-cache/lib/libvarnish/vtim.c
1
/*-
2
 * Copyright (c) 2006 Verdens Gang AS
3
 * Copyright (c) 2006-2011 Varnish Software AS
4
 * All rights reserved.
5
 *
6
 * Author: Poul-Henning Kamp <phk@phk.freebsd.dk>
7
 *
8
 * Redistribution and use in source and binary forms, with or without
9
 * modification, are permitted provided that the following conditions
10
 * are met:
11
 * 1. Redistributions of source code must retain the above copyright
12
 *    notice, this list of conditions and the following disclaimer.
13
 * 2. Redistributions in binary form must reproduce the above copyright
14
 *    notice, this list of conditions and the following disclaimer in the
15
 *    documentation and/or other materials provided with the distribution.
16
 *
17
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20
 * ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
21
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27
 * SUCH DAMAGE.
28
 *
29
 * Semi-trivial functions to handle HTTP header timestamps according to
30
 * RFC 2616 section 3.3.
31
 *
32
 * We must parse four different formats:
33
 *       000000000011111111112222222222
34
 *       012345678901234567890123456789
35
 *       ------------------------------
36
 *      "Sun, 06 Nov 1994 08:49:37 GMT"         RFC822 & RFC1123
37
 *      "Sunday, 06-Nov-94 08:49:37 GMT"        RFC850
38
 *      "Sun Nov  6 08:49:37 1994"              ANSI-C asctime()
39
 *      "1994-11-06T08:49:37"                   ISO 8601
40
 *
41
 * And always output the RFC1123 format.
42
 *
43
 * So why are these functions hand-built ?
44
 *
45
 * Because the people behind POSIX were short-sighted morons who didn't think
46
 * anybody would ever need to deal with timestamps in multiple different
47
 * timezones at the same time -- for that matter, convert timestamps to
48
 * broken down UTC/GMT time.
49
 *
50
 * We could, and used to, get by by smashing our TZ variable to "UTC" but
51
 * that ruins the LOCALE for VMODs.
52
 *
53
 */
54
55
#include "config.h"
56
57
#include <time.h>
58
#include <sys/time.h>
59
60
#include <math.h>
61
#include <stdio.h>
62
#include <stdlib.h>
63
#include <string.h>
64
#ifdef __MACH__
65
#include <mach/mach_time.h>
66
#endif
67
68
#include "vdef.h"
69
70
#include "vas.h"
71
#include "vtim.h"
72
73
/* relax vtim parsing */
74
unsigned VTIM_postel = 0;
75
76
static const char * const weekday_name[] = {
77
        "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
78
};
79
80
static const char * const more_weekday[] = {
81
        "day", "day", "sday", "nesday", "rsday", "day", "urday"
82
};
83
84
static const char * const month_name[] = {
85
        "Jan", "Feb", "Mar", "Apr", "May", "Jun",
86
        "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
87
};
88
89
static const int days_in_month[] = {
90
        31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
91
};
92
93
static const int days_before_month[] = {
94
        0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334
95
};
96
97
#ifdef __MACH__
98
// http://stackoverflow.com/a/21352348
99
static uint64_t mt_base;
100
static double   mt_scale;
101
102
static void
103
mach_time_init(void)
104
{
105
        mach_timebase_info_data_t timebase;
106
107
        mt_base = mach_absolute_time();
108
109
        AZ(mach_timebase_info(&timebase));
110
        mt_scale = (double)timebase.numer / (double)timebase.denom * 1e-9;
111
}
112
113
static __attribute__((constructor)) void
114
init(void)
115
{
116
        mach_time_init();
117
}
118
#endif
119
120
/*
121
 * Note on Solaris: for some reason, clock_gettime(CLOCK_MONOTONIC, &ts) is not
122
 * implemented in assembly, but falls into a syscall, while gethrtime() doesn't,
123
 * so we save a syscall by using gethrtime() if it is defined.
124
 */
125
126
double
127 421503
VTIM_mono(void)
128
{
129
#ifdef HAVE_GETHRTIME
130
        return (gethrtime() * 1e-9);
131
#elif  HAVE_CLOCK_GETTIME
132
        struct timespec ts;
133
134 421503
        AZ(clock_gettime(CLOCK_MONOTONIC, &ts));
135 421554
        return (ts.tv_sec + 1e-9 * ts.tv_nsec);
136
#elif  defined(__MACH__)
137
        uint64_t mt = mach_absolute_time() - mt_base;
138
139
        return (mt * mt_scale);
140
#else
141
#error Varnish needs some monotonic time source
142
#endif
143
}
144
145
double
146 126128
VTIM_real(void)
147
{
148
#ifdef HAVE_CLOCK_GETTIME
149
        struct timespec ts;
150
151 126128
        AZ(clock_gettime(CLOCK_REALTIME, &ts));
152 126168
        return (ts.tv_sec + 1e-9 * ts.tv_nsec);
153
#else
154
        struct timeval tv;
155
156
        AZ(gettimeofday(&tv, NULL));
157
        return (tv.tv_sec + 1e-6 * tv.tv_usec);
158
#endif
159
}
160
161
void
162 2840
VTIM_format(double t, char *p)
163
{
164
        struct tm tm;
165
        time_t tt;
166
167 2840
        tt = (time_t) t;
168 2840
        (void)gmtime_r(&tt, &tm);
169 2840
        AN(snprintf(p, VTIM_FORMAT_SIZE, "%s, %02d %s %4d %02d:%02d:%02d GMT",
170
            weekday_name[tm.tm_wday], tm.tm_mday, month_name[tm.tm_mon],
171
            tm.tm_year + 1900, tm.tm_hour, tm.tm_min, tm.tm_sec));
172 2840
}
173
174
#ifdef TEST_DRIVER
175
#define FAIL()  \
176
        do { printf("\nFAIL <<%d>>\n", __LINE__); return (0); } while (0)
177
#else
178
#define FAIL()  \
179
        do { return (0); } while (0)
180
#endif
181
182
#define DIGIT(mult, fld)                                        \
183
        do {                                                    \
184
                if (*p < '0' || *p > '9')                       \
185
                        FAIL();                                 \
186
                fld += (*p - '0') * mult;                       \
187
                p++;                                            \
188
        } while(0)
189
190
#define MUSTBE(chr)                                             \
191
        do {                                                    \
192
                if (*p != chr)                                  \
193
                        FAIL();                                 \
194
                p++;                                            \
195
        } while(0)
196
197
#define WEEKDAY()                                               \
198
        do {                                                    \
199
                int i;                                          \
200
                for (i = 0; i < 7; i++) {                       \
201
                        if (!memcmp(p, weekday_name[i], 3)) {   \
202
                                weekday = i;                    \
203
                                break;                          \
204
                        }                                       \
205
                }                                               \
206
                if (i == 7)                                     \
207
                        FAIL();                                 \
208
                p += 3;                                         \
209
        } while(0)
210
211
212
#define MONTH()                                                 \
213
        do {                                                    \
214
                int i;                                          \
215
                for (i = 0; i < 12; i++) {                      \
216
                        if (!memcmp(p, month_name[i], 3)) {     \
217
                                month = i + 1;                  \
218
                                break;                          \
219
                        }                                       \
220
                }                                               \
221
                if (i == 12)                                    \
222
                        FAIL();                                 \
223
                p += 3;                                         \
224
        } while(0)
225
226
#define TIMESTAMP()                                             \
227
        do {                                                    \
228
                DIGIT(10, hour);                                \
229
                DIGIT(1, hour);                                 \
230
                MUSTBE(':');                                    \
231
                DIGIT(10, min);                                 \
232
                DIGIT(1, min);                                  \
233
                MUSTBE(':');                                    \
234
                DIGIT(10, sec);                                 \
235
                DIGIT(1, sec);                                  \
236
        } while(0)
237
238
double
239 3644
VTIM_parse(const char *p)
240
{
241
        double t;
242 3644
        int month = 0, year = 0, weekday = -1, mday = 0;
243 3644
        int hour = 0, min = 0, sec = 0;
244
        int d, leap;
245
246 3644
        if (p == NULL || *p == '\0')
247 3
                FAIL();
248
249 7284
        while (*p == ' ')
250 2
                p++;
251
252 3641
        if (*p >= '0' && *p <= '9') {
253
                /* ISO8601 -- "1994-11-06T08:49:37" */
254 12
                DIGIT(1000, year);
255 12
                DIGIT(100, year);
256 12
                DIGIT(10, year);
257 12
                DIGIT(1, year);
258 12
                MUSTBE('-');
259 6
                DIGIT(10, month);
260 6
                DIGIT(1, month);
261 6
                MUSTBE('-');
262 6
                DIGIT(10, mday);
263 6
                DIGIT(1, mday);
264 6
                MUSTBE('T');
265 6
                TIMESTAMP();
266
        } else {
267 3629
                WEEKDAY();
268 3627
                assert(weekday >= 0 && weekday <= 6);
269 3627
                if (*p == ',') {
270
                        /* RFC822 & RFC1123 - "Sun, 06 Nov 1994 08:49:37 GMT" */
271 1985
                        p++;
272 1985
                        MUSTBE(' ');
273 1985
                        if (VTIM_postel && *p && p[1] == ' ')
274 3
                                DIGIT(1, mday);
275
                        else {
276 1982
                                DIGIT(10, mday);
277 1982
                                DIGIT(1, mday);
278
                        }
279 1985
                        MUSTBE(' ');
280 1985
                        MONTH();
281 1985
                        MUSTBE(' ');
282 1985
                        DIGIT(1000, year);
283 1985
                        DIGIT(100, year);
284 1985
                        DIGIT(10, year);
285 1985
                        DIGIT(1, year);
286 1985
                        MUSTBE(' ');
287 1985
                        TIMESTAMP();
288 1985
                        MUSTBE(' ');
289 1985
                        MUSTBE('G');
290 1985
                        MUSTBE('M');
291 1985
                        MUSTBE('T');
292 1642
                } else if (*p == ' ') {
293
                        /* ANSI-C asctime() -- "Sun Nov  6 08:49:37 1994" */
294 818
                        p++;
295 818
                        MONTH();
296 815
                        MUSTBE(' ');
297 815
                        if (*p != ' ')
298 3
                                DIGIT(10, mday);
299
                        else
300 812
                                p++;
301 815
                        DIGIT(1, mday);
302 815
                        MUSTBE(' ');
303 815
                        TIMESTAMP();
304 815
                        MUSTBE(' ');
305 815
                        DIGIT(1000, year);
306 815
                        DIGIT(100, year);
307 815
                        DIGIT(10, year);
308 815
                        DIGIT(1, year);
309 824
                } else if (!memcmp(p, more_weekday[weekday],
310
                    strlen(more_weekday[weekday]))) {
311
                        /* RFC850 -- "Sunday, 06-Nov-94 08:49:37 GMT" */
312 821
                        p += strlen(more_weekday[weekday]);
313 821
                        MUSTBE(',');
314 821
                        MUSTBE(' ');
315 821
                        DIGIT(10, mday);
316 821
                        DIGIT(1, mday);
317 821
                        MUSTBE('-');
318 821
                        MONTH();
319 821
                        MUSTBE('-');
320 821
                        DIGIT(10, year);
321 821
                        DIGIT(1, year);
322 821
                        year += 1900;
323 821
                        if (year < 1969)
324 9
                                year += 100;
325 821
                        MUSTBE(' ');
326 821
                        TIMESTAMP();
327 821
                        MUSTBE(' ');
328 821
                        MUSTBE('G');
329 821
                        MUSTBE('M');
330 821
                        MUSTBE('T');
331
                } else
332 3
                        FAIL();
333
        }
334
335 7257
        while (*p == ' ')
336 3
                p++;
337
338 3627
        if (*p != '\0')
339 3
                FAIL();
340
341 3624
        if (sec < 0 || sec > 60)        /* Leapseconds! */
342 3
                FAIL();
343 3621
        if (min < 0 || min > 59)
344 3
                FAIL();
345 3618
        if (hour < 0 || hour > 23)
346 3
                FAIL();
347 3615
        if (month < 1 || month > 12)
348 3
                FAIL();
349 3612
        if (mday < 1 || mday > days_in_month[month - 1])
350 3
                FAIL();
351 3609
        if (year < 1899)
352 0
                FAIL();
353
354 3609
        leap =
355 3609
            ((year) % 4) == 0 && (((year) % 100) != 0 || ((year) % 400) == 0);
356
357 3609
        if (month == 2 && mday > 28 && !leap)
358 3
                FAIL();
359
360 3606
        if (sec == 60)                  /* Ignore Leapseconds */
361 3
                sec--;
362
363 3606
        t = ((hour * 60.) + min) * 60. + sec;
364
365 3606
        d = (mday - 1) + days_before_month[month - 1];
366
367 3606
        if (month > 2 && leap)
368 62
                d++;
369
370 3606
        d += (year % 100) * 365;        /* There are 365 days in a year */
371
372 3606
        if ((year % 100) > 0)           /* And a leap day every four years */
373 3606
                d += (((year % 100) - 1) / 4);
374
375 3606
        d += ((year / 100) - 20) *      /* Days relative to y2000 */
376
            (100 * 365 + 24);           /* 24 leapdays per year in a century */
377
378 3606
        d += ((year - 1) / 400) - 4;    /* And one more every 400 years */
379
380
        /*
381
         * Now check weekday, if we have one.
382
         * 6 is because 2000-01-01 was a saturday.
383
         * 10000 is to make sure the modulus argument is always positive
384
         */
385 3606
        if (weekday != -1 && (d + 6 + 7 * 10000) % 7 != weekday)
386 6
                FAIL();
387
388 3600
        t += d * 86400.;
389
390 3600
        t += 10957. * 86400.;           /* 10957 days frm UNIX epoch to y2000 */
391
392 3600
        return (t);
393
}
394
395
void
396 122110
VTIM_sleep(double t)
397
{
398
#ifdef HAVE_NANOSLEEP
399
        struct timespec ts;
400
401 122110
        ts = VTIM_timespec(t);
402
403 122109
        (void)nanosleep(&ts, NULL);
404
#else
405
        if (t >= 1.) {
406
                (void)sleep(floor(t));
407
                t -= floor(t);
408
        }
409
        /* XXX: usleep() is not mandated to be thread safe */
410
        t *= 1e6;
411
        if (t > 0)
412
                (void)usleep(floor(t));
413
#endif
414 118069
}
415
416
struct timeval
417 3572
VTIM_timeval(double t)
418
{
419
        struct timeval tv;
420
421 3572
        AZ(isnan(t));
422 3572
        tv.tv_sec = (time_t)trunc(t);
423 3572
        tv.tv_usec = (int)(1e6 * (t - tv.tv_sec));
424 3572
        return (tv);
425
}
426
427
struct timespec
428 122087
VTIM_timespec(double t)
429
{
430
        struct timespec tv;
431
432 122087
        AZ(isnan(t));
433 122141
        tv.tv_sec = (time_t)trunc(t);
434 122141
        tv.tv_nsec = (int)(1e9 * (t - tv.tv_sec));
435 122141
        return (tv);
436
}
437
438
439
#ifdef TEST_DRIVER
440
441
/**********************************************************************
442
 * Compile with:
443
 *      cc -o foo -DTEST_DRIVER -I../.. -I../../include vtim.c vas.c -lm
444
 * Test with:
445
 *      ./foo
446
 */
447
448
#include <stdint.h>
449
450
static void
451
tst(const char *s, time_t good)
452
{
453
        time_t t;
454
        char buf[BUFSIZ];
455
456
        t = VTIM_parse(s);
457
        VTIM_format(t, buf);
458
        printf("%-30s -> %12jd -> %s\n", s, (intmax_t)t, buf);
459
        if (t != good) {
460
                printf("Parse error! Got: %jd should have %jd diff %jd\n",
461
                    (intmax_t)t, (intmax_t)good, (intmax_t)(t - good));
462
                exit(4);
463
        }
464
}
465
466
static int
467
tst_delta_check(const char *name, double begin, double end, double ref)
468
{
469
        const double tol_max = 1.1;
470
        const double tol_min = 1;
471
472
        printf("%s delta for %fs sleep: %f\n", name, ref, (end - begin));
473
474
        if ((end - begin) > tol_max * ref) {
475
                printf("%s delta above tolerance: ((%f - %f) = %f) > %f\n",
476
                    name, end, begin, (end - begin), tol_max);
477
                return (1);
478
        } else if ((end - begin) < tol_min * ref) {
479
                printf("%s delta below tolerance: ((%f - %f) = %f) < %f\n",
480
                    name, end, begin, (end - begin), tol_min);
481
                return (1);
482
        }
483
        return (0);
484
}
485
486
static void
487
tst_delta()
488
{
489
        double m_begin, m_end;
490
        double r_begin, r_end;
491
        const double ref = 1;
492
        int err = 0;
493
494
        r_begin = VTIM_real();
495
        m_begin = VTIM_mono();
496
        VTIM_sleep(ref);
497
        r_end = VTIM_real();
498
        m_end = VTIM_mono();
499
500
        err += tst_delta_check("VTIM_mono", m_begin, m_end, ref);
501
        err += tst_delta_check("VTIM_real", r_begin, r_end, ref);
502
503
        if (err) {
504
                printf("%d time delta test errors\n", err);
505
                exit(4);
506
        }
507
}
508
509
static void
510
bench()
511
{
512
        double s, e, t;
513
        int i;
514
515
        t = 0;
516
        s = VTIM_real();
517
        for (i=0; i<100000; i++)
518
                t += VTIM_real();
519
        e = VTIM_real();
520
        printf("real: %fs / %d = %fns - tst val %f\n",
521
            e - s, i, 1e9 * (e - s) / i, t);
522
523
        t = 0;
524
        s = VTIM_real();
525
        for (i=0; i<100000; i++)
526
                t += VTIM_mono();
527
        e = VTIM_real();
528
        printf("mono: %fs / %d = %fns - tst val %f\n",
529
            e - s, i, 1e9 * (e - s) / i, t);
530
}
531
532
int
533
main(int argc, char **argv)
534
{
535
        time_t t;
536
        struct tm tm;
537
        double tt;
538
        char buf[BUFSIZ];
539
        char buf1[BUFSIZ];
540
541
        AZ(setenv("TZ", "UTC", 1));
542
        assert(sizeof t >= 8);
543
544
        bench();
545
546
        /* Brute force test against libc version */
547
        for (t = -2209852800; t < 20000000000; t += 3599) {
548
                gmtime_r(&t, &tm);
549
                strftime(buf1, sizeof buf1, "%a, %d %b %Y %T GMT", &tm);
550
                VTIM_format(t, buf);
551
                if (strcmp(buf, buf1)) {
552
                        printf("libc: <%s> Vtim <%s> %jd\n",
553
                            buf1, buf, (intmax_t)t);
554
                        exit(2);
555
                }
556
                tt = VTIM_parse(buf1);
557
                if (tt != t) {
558
                        VTIM_format(tt, buf);
559
                        printf("  fm: %12jd <%s>\n", (intmax_t)t, buf1);
560
                        printf("  to: %12.0f <%s>\n", tt, buf);
561
                        exit(2);
562
                }
563
564
                strftime(buf1, sizeof buf1, "%a %b %e %T %Y", &tm);
565
                tt = VTIM_parse(buf1);
566
                if (tt != t) {
567
                        VTIM_format(tt, buf);
568
                        printf("  fm: %12jd <%s>\n", (intmax_t)t, buf1);
569
                        printf("  to: %12.0f <%s>\n", tt, buf);
570
                        exit(2);
571
                }
572
573
                strftime(buf1, sizeof buf1, "%Y-%m-%dT%T", &tm);
574
                tt = VTIM_parse(buf1);
575
                if (tt != t) {
576
                        VTIM_format(tt, buf);
577
                        printf("  fm: %12jd <%s>\n", (intmax_t)t, buf1);
578
                        printf("  to: %12.0f <%s>\n", tt, buf);
579
                        exit(2);
580
                }
581
582
                if (tm.tm_year >= 69 && tm.tm_year < 169) {
583
                        strftime(buf1, sizeof buf1, "%A, %d-%b-%y %T GMT", &tm);
584
                        tt = VTIM_parse(buf1);
585
                        if (tt != t) {
586
                                VTIM_format(tt, buf);
587
                                printf("  fm: %12jd <%s>\n", (intmax_t)t, buf1);
588
                                printf("  to: %12.0f <%s>\n", tt, buf);
589
                                exit(2);
590
                        }
591
                }
592
        }
593
594
        /* Examples from RFC2616 section 3.3.1 */
595
        tst("Sun, 06 Nov 1994 08:49:37 GMT", 784111777);
596
        tst("Sunday, 06-Nov-94 08:49:37 GMT", 784111777);
597
        tst("Sun Nov  6 08:49:37 1994", 784111777);
598
599
        tst("1994-11-06T08:49:37", 784111777);
600
601
        tst_delta();
602
603
        return (0);
604
}
605
#endif