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
static const char * const weekday_name[] = {
74
        "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
75
};
76
77
static const char * const more_weekday[] = {
78
        "day", "day", "sday", "nesday", "rsday", "day", "urday"
79
};
80
81
static const char * const month_name[] = {
82
        "Jan", "Feb", "Mar", "Apr", "May", "Jun",
83
        "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
84
};
85
86
static const int days_in_month[] = {
87
        31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
88
};
89
90
static const int days_before_month[] = {
91
        0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334
92
};
93
94
#ifdef __MACH__
95
// http://stackoverflow.com/a/21352348
96
static uint64_t mt_base;
97
static double   mt_scale;
98
99
static void
100
mach_time_init(void)
101
{
102
        mach_timebase_info_data_t timebase;
103
104
        mt_base = mach_absolute_time();
105
106
        AZ(mach_timebase_info(&timebase));
107
        mt_scale = (double)timebase.numer / (double)timebase.denom * 1e-9;
108
}
109
110
static __attribute__((constructor)) void
111
init(void)
112
{
113
        mach_time_init();
114
}
115
#endif
116
117
/*
118
 * Note on Solaris: for some reason, clock_gettime(CLOCK_MONOTONIC, &ts) is not
119
 * implemented in assembly, but falls into a syscall, while gethrtime() doesn't,
120
 * so we save a syscall by using gethrtime() if it is defined.
121
 */
122
123
double
124 352717
VTIM_mono(void)
125
{
126
#ifdef HAVE_GETHRTIME
127
        return (gethrtime() * 1e-9);
128
#elif  HAVE_CLOCK_GETTIME
129
        struct timespec ts;
130
131 352717
        AZ(clock_gettime(CLOCK_MONOTONIC, &ts));
132 352731
        return (ts.tv_sec + 1e-9 * ts.tv_nsec);
133
#elif  defined(__MACH__)
134
        uint64_t mt = mach_absolute_time() - mt_base;
135
136
        return (mt * mt_scale);
137
#else
138
#error Varnish needs some monotonic time source
139
#endif
140
}
141
142
double
143 106696
VTIM_real(void)
144
{
145
#ifdef HAVE_CLOCK_GETTIME
146
        struct timespec ts;
147
148 106696
        AZ(clock_gettime(CLOCK_REALTIME, &ts));
149 106678
        return (ts.tv_sec + 1e-9 * ts.tv_nsec);
150
#else
151
        struct timeval tv;
152
153
        AZ(gettimeofday(&tv, NULL));
154
        return (tv.tv_sec + 1e-6 * tv.tv_usec);
155
#endif
156
}
157
158
void
159 2258
VTIM_format(double t, char *p)
160
{
161
        struct tm tm;
162
        time_t tt;
163
164 2258
        tt = (time_t) t;
165 2258
        (void)gmtime_r(&tt, &tm);
166 2258
        AN(snprintf(p, VTIM_FORMAT_SIZE, "%s, %02d %s %4d %02d:%02d:%02d GMT",
167
            weekday_name[tm.tm_wday], tm.tm_mday, month_name[tm.tm_mon],
168
            tm.tm_year + 1900, tm.tm_hour, tm.tm_min, tm.tm_sec));
169 2258
}
170
171
#ifdef TEST_DRIVER
172
#define FAIL()  \
173
        do { printf("\nFAIL <<%d>>\n", __LINE__); return (0); } while (0)
174
#else
175
#define FAIL()  \
176
        do { return (0); } while (0)
177
#endif
178
179
#define DIGIT(mult, fld)                                        \
180
        do {                                                    \
181
                if (*p < '0' || *p > '9')                       \
182
                        FAIL();                                 \
183
                fld += (*p - '0') * mult;                       \
184
                p++;                                            \
185
        } while(0)
186
187
#define MUSTBE(chr)                                             \
188
        do {                                                    \
189
                if (*p != chr)                                  \
190
                        FAIL();                                 \
191
                p++;                                            \
192
        } while(0)
193
194
#define WEEKDAY()                                               \
195
        do {                                                    \
196
                int i;                                          \
197
                for (i = 0; i < 7; i++) {                       \
198
                        if (!memcmp(p, weekday_name[i], 3)) {   \
199
                                weekday = i;                    \
200
                                break;                          \
201
                        }                                       \
202
                }                                               \
203
                if (i == 7)                                     \
204
                        FAIL();                                 \
205
                p += 3;                                         \
206
        } while(0)
207
208
209
#define MONTH()                                                 \
210
        do {                                                    \
211
                int i;                                          \
212
                for (i = 0; i < 12; i++) {                      \
213
                        if (!memcmp(p, month_name[i], 3)) {     \
214
                                month = i + 1;                  \
215
                                break;                          \
216
                        }                                       \
217
                }                                               \
218
                if (i == 12)                                    \
219
                        FAIL();                                 \
220
                p += 3;                                         \
221
        } while(0)
222
223
#define TIMESTAMP()                                             \
224
        do {                                                    \
225
                DIGIT(10, hour);                                \
226
                DIGIT(1, hour);                                 \
227
                MUSTBE(':');                                    \
228
                DIGIT(10, min);                                 \
229
                DIGIT(1, min);                                  \
230
                MUSTBE(':');                                    \
231
                DIGIT(10, sec);                                 \
232
                DIGIT(1, sec);                                  \
233
        } while(0)
234
235
double
236 3218
VTIM_parse(const char *p)
237
{
238
        double t;
239 3218
        int month = 0, year = 0, weekday = -1, mday = 0;
240 3218
        int hour = 0, min = 0, sec = 0;
241
        int d, leap;
242
243 6438
        while (*p == ' ')
244 2
                p++;
245
246 3218
        if (*p >= '0' && *p <= '9') {
247
                /* ISO8601 -- "1994-11-06T08:49:37" */
248 12
                DIGIT(1000, year);
249 12
                DIGIT(100, year);
250 12
                DIGIT(10, year);
251 12
                DIGIT(1, year);
252 12
                MUSTBE('-');
253 6
                DIGIT(10, month);
254 6
                DIGIT(1, month);
255 6
                MUSTBE('-');
256 6
                DIGIT(10, mday);
257 6
                DIGIT(1, mday);
258 6
                MUSTBE('T');
259 6
                TIMESTAMP();
260
        } else {
261 3206
                WEEKDAY();
262 3204
                assert(weekday >= 0 && weekday <= 6);
263 3204
                if (*p == ',') {
264
                        /* RFC822 & RFC1123 - "Sun, 06 Nov 1994 08:49:37 GMT" */
265 1782
                        p++;
266 1782
                        MUSTBE(' ');
267 1782
                        DIGIT(10, mday);
268 1782
                        DIGIT(1, mday);
269 1782
                        MUSTBE(' ');
270 1782
                        MONTH();
271 1782
                        MUSTBE(' ');
272 1782
                        DIGIT(1000, year);
273 1782
                        DIGIT(100, year);
274 1782
                        DIGIT(10, year);
275 1782
                        DIGIT(1, year);
276 1782
                        MUSTBE(' ');
277 1782
                        TIMESTAMP();
278 1782
                        MUSTBE(' ');
279 1782
                        MUSTBE('G');
280 1782
                        MUSTBE('M');
281 1782
                        MUSTBE('T');
282 1422
                } else if (*p == ' ') {
283
                        /* ANSI-C asctime() -- "Sun Nov  6 08:49:37 1994" */
284 708
                        p++;
285 708
                        MONTH();
286 705
                        MUSTBE(' ');
287 705
                        if (*p != ' ')
288 3
                                DIGIT(10, mday);
289
                        else
290 702
                                p++;
291 705
                        DIGIT(1, mday);
292 705
                        MUSTBE(' ');
293 705
                        TIMESTAMP();
294 705
                        MUSTBE(' ');
295 705
                        DIGIT(1000, year);
296 705
                        DIGIT(100, year);
297 705
                        DIGIT(10, year);
298 705
                        DIGIT(1, year);
299 714
                } else if (!memcmp(p, more_weekday[weekday],
300
                    strlen(more_weekday[weekday]))) {
301
                        /* RFC850 -- "Sunday, 06-Nov-94 08:49:37 GMT" */
302 711
                        p += strlen(more_weekday[weekday]);
303 711
                        MUSTBE(',');
304 711
                        MUSTBE(' ');
305 711
                        DIGIT(10, mday);
306 711
                        DIGIT(1, mday);
307 711
                        MUSTBE('-');
308 711
                        MONTH();
309 711
                        MUSTBE('-');
310 711
                        DIGIT(10, year);
311 711
                        DIGIT(1, year);
312 711
                        year += 1900;
313 711
                        if (year < 1969)
314 9
                                year += 100;
315 711
                        MUSTBE(' ');
316 711
                        TIMESTAMP();
317 711
                        MUSTBE(' ');
318 711
                        MUSTBE('G');
319 711
                        MUSTBE('M');
320 711
                        MUSTBE('T');
321
                } else
322 3
                        FAIL();
323
        }
324
325 6411
        while (*p == ' ')
326 3
                p++;
327
328 3204
        if (*p != '\0')
329 3
                FAIL();
330
331 3201
        if (sec < 0 || sec > 60)        /* Leapseconds! */
332 3
                FAIL();
333 3198
        if (min < 0 || min > 59)
334 3
                FAIL();
335 3195
        if (hour < 0 || hour > 23)
336 3
                FAIL();
337 3192
        if (month < 1 || month > 12)
338 3
                FAIL();
339 3189
        if (mday < 1 || mday > days_in_month[month - 1])
340 3
                FAIL();
341 3186
        if (year < 1899)
342 0
                FAIL();
343
344 3186
        leap =
345 3186
            ((year) % 4) == 0 && (((year) % 100) != 0 || ((year) % 400) == 0);
346
347 3186
        if (month == 2 && mday > 28 && !leap)
348 3
                FAIL();
349
350 3183
        if (sec == 60)                  /* Ignore Leapseconds */
351 3
                sec--;
352
353 3183
        t = ((hour * 60.) + min) * 60. + sec;
354
355 3183
        d = (mday - 1) + days_before_month[month - 1];
356
357 3183
        if (month > 2 && leap)
358 62
                d++;
359
360 3183
        d += (year % 100) * 365;        /* There are 365 days in a year */
361
362 3183
        if ((year % 100) > 0)           /* And a leap day every four years */
363 3183
                d += (((year % 100) - 1) / 4);
364
365 3183
        d += ((year / 100) - 20) *      /* Days relative to y2000 */
366
            (100 * 365 + 24);           /* 24 leapdays per year in a century */
367
368 3183
        d += ((year - 1) / 400) - 4;    /* And one more every 400 years */
369
370
        /*
371
         * Now check weekday, if we have one.
372
         * 6 is because 2000-01-01 was a saturday.
373
         * 10000 is to make sure the modulus argument is always positive
374
         */
375 3183
        if (weekday != -1 && (d + 6 + 7 * 10000) % 7 != weekday)
376 6
                FAIL();
377
378 3177
        t += d * 86400.;
379
380 3177
        t += 10957. * 86400.;           /* 10957 days frm UNIX epoch to y2000 */
381
382 3177
        return (t);
383
}
384
385
void
386 109330
VTIM_sleep(double t)
387
{
388
#ifdef HAVE_NANOSLEEP
389
        struct timespec ts;
390
391 109330
        ts = VTIM_timespec(t);
392
393 109365
        (void)nanosleep(&ts, NULL);
394
#else
395
        if (t >= 1.) {
396
                (void)sleep(floor(t));
397
                t -= floor(t);
398
        }
399
        /* XXX: usleep() is not mandated to be thread safe */
400
        t *= 1e6;
401
        if (t > 0)
402
                (void)usleep(floor(t));
403
#endif
404 105709
}
405
406
struct timeval
407 2996
VTIM_timeval(double t)
408
{
409
        struct timeval tv;
410
411 2996
        AZ(isnan(t));
412 2996
        tv.tv_sec = (time_t)trunc(t);
413 2996
        tv.tv_usec = (int)(1e6 * (t - tv.tv_sec));
414 2996
        return (tv);
415
}
416
417
struct timespec
418 109353
VTIM_timespec(double t)
419
{
420
        struct timespec tv;
421
422 109353
        AZ(isnan(t));
423 109361
        tv.tv_sec = (time_t)trunc(t);
424 109361
        tv.tv_nsec = (int)(1e9 * (t - tv.tv_sec));
425 109361
        return (tv);
426
}
427
428
429
#ifdef TEST_DRIVER
430
431
/**********************************************************************
432
 * Compile with:
433
 *      cc -o foo -DTEST_DRIVER -I../.. -I../../include vtim.c vas.c -lm
434
 * Test with:
435
 *      ./foo
436
 */
437
438
#include <stdint.h>
439
440
static void
441
tst(const char *s, time_t good)
442
{
443
        time_t t;
444
        char buf[BUFSIZ];
445
446
        t = VTIM_parse(s);
447
        VTIM_format(t, buf);
448
        printf("%-30s -> %12jd -> %s\n", s, (intmax_t)t, buf);
449
        if (t != good) {
450
                printf("Parse error! Got: %jd should have %jd diff %jd\n",
451
                    (intmax_t)t, (intmax_t)good, (intmax_t)(t - good));
452
                exit(4);
453
        }
454
}
455
456
static int
457
tst_delta_check(const char *name, double begin, double end, double ref)
458
{
459
        const double tol_max = 1.1;
460
        const double tol_min = 1;
461
462
        printf("%s delta for %fs sleep: %f\n", name, ref, (end - begin));
463
464
        if ((end - begin) > tol_max * ref) {
465
                printf("%s delta above tolerance: ((%f - %f) = %f) > %f\n",
466
                    name, end, begin, (end - begin), tol_max);
467
                return (1);
468
        } else if ((end - begin) < tol_min * ref) {
469
                printf("%s delta below tolerance: ((%f - %f) = %f) < %f\n",
470
                    name, end, begin, (end - begin), tol_min);
471
                return (1);
472
        }
473
        return (0);
474
}
475
476
static void
477
tst_delta()
478
{
479
        double m_begin, m_end;
480
        double r_begin, r_end;
481
        const double ref = 1;
482
        int err = 0;
483
484
        r_begin = VTIM_real();
485
        m_begin = VTIM_mono();
486
        VTIM_sleep(ref);
487
        r_end = VTIM_real();
488
        m_end = VTIM_mono();
489
490
        err += tst_delta_check("VTIM_mono", m_begin, m_end, ref);
491
        err += tst_delta_check("VTIM_real", r_begin, r_end, ref);
492
493
        if (err) {
494
                printf("%d time delta test errors\n", err);
495
                exit(4);
496
        }
497
}
498
499
static void
500
bench()
501
{
502
        double s, e, t;
503
        int i;
504
505
        t = 0;
506
        s = VTIM_real();
507
        for (i=0; i<100000; i++)
508
                t += VTIM_real();
509
        e = VTIM_real();
510
        printf("real: %fs / %d = %fns - tst val %f\n",
511
            e - s, i, 1e9 * (e - s) / i, t);
512
513
        t = 0;
514
        s = VTIM_real();
515
        for (i=0; i<100000; i++)
516
                t += VTIM_mono();
517
        e = VTIM_real();
518
        printf("mono: %fs / %d = %fns - tst val %f\n",
519
            e - s, i, 1e9 * (e - s) / i, t);
520
}
521
522
int
523
main(int argc, char **argv)
524
{
525
        time_t t;
526
        struct tm tm;
527
        double tt;
528
        char buf[BUFSIZ];
529
        char buf1[BUFSIZ];
530
531
        AZ(setenv("TZ", "UTC", 1));
532
        assert(sizeof t >= 8);
533
534
        bench();
535
536
        /* Brute force test against libc version */
537
        for (t = -2209852800; t < 20000000000; t += 3599) {
538
                gmtime_r(&t, &tm);
539
                strftime(buf1, sizeof buf1, "%a, %d %b %Y %T GMT", &tm);
540
                VTIM_format(t, buf);
541
                if (strcmp(buf, buf1)) {
542
                        printf("libc: <%s> Vtim <%s> %jd\n",
543
                            buf1, buf, (intmax_t)t);
544
                        exit(2);
545
                }
546
                tt = VTIM_parse(buf1);
547
                if (tt != t) {
548
                        VTIM_format(tt, buf);
549
                        printf("  fm: %12jd <%s>\n", (intmax_t)t, buf1);
550
                        printf("  to: %12.0f <%s>\n", tt, buf);
551
                        exit(2);
552
                }
553
554
                strftime(buf1, sizeof buf1, "%a %b %e %T %Y", &tm);
555
                tt = VTIM_parse(buf1);
556
                if (tt != t) {
557
                        VTIM_format(tt, buf);
558
                        printf("  fm: %12jd <%s>\n", (intmax_t)t, buf1);
559
                        printf("  to: %12.0f <%s>\n", tt, buf);
560
                        exit(2);
561
                }
562
563
                strftime(buf1, sizeof buf1, "%Y-%m-%dT%T", &tm);
564
                tt = VTIM_parse(buf1);
565
                if (tt != t) {
566
                        VTIM_format(tt, buf);
567
                        printf("  fm: %12jd <%s>\n", (intmax_t)t, buf1);
568
                        printf("  to: %12.0f <%s>\n", tt, buf);
569
                        exit(2);
570
                }
571
572
                if (tm.tm_year >= 69 && tm.tm_year < 169) {
573
                        strftime(buf1, sizeof buf1, "%A, %d-%b-%y %T GMT", &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
        }
583
584
        /* Examples from RFC2616 section 3.3.1 */
585
        tst("Sun, 06 Nov 1994 08:49:37 GMT", 784111777);
586
        tst("Sunday, 06-Nov-94 08:49:37 GMT", 784111777);
587
        tst("Sun Nov  6 08:49:37 1994", 784111777);
588
589
        tst("1994-11-06T08:49:37", 784111777);
590
591
        tst_delta();
592
593
        return (0);
594
}
595
#endif