Coverage Report

Created: 2023-03-26 06:28

/src/httpd/server/util_time.c
Line
Count
Source (jump to first uncovered line)
1
/* Licensed to the Apache Software Foundation (ASF) under one or more
2
 * contributor license agreements.  See the NOTICE file distributed with
3
 * this work for additional information regarding copyright ownership.
4
 * The ASF licenses this file to You under the Apache License, Version 2.0
5
 * (the "License"); you may not use this file except in compliance with
6
 * the License.  You may obtain a copy of the License at
7
 *
8
 *     http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16
17
#include "util_time.h"
18
#include "apr_env.h"
19
20
21
22
/* Number of characters needed to format the microsecond part of a timestamp.
23
 * Microseconds have 6 digits plus one separator character makes 7.
24
 *   */
25
0
#define AP_CTIME_USEC_LENGTH      7
26
27
/* Length of ISO 8601 date/time (including trailing '\0') */
28
0
#define AP_CTIME_COMPACT_LEN      20
29
30
/* Length of timezone offset from GMT ([+-]hhmm) plus leading space */
31
0
#define AP_CTIME_GMTOFF_LEN       6
32
33
/* Cache for exploded values of recent timestamps
34
 */
35
36
struct exploded_time_cache_element {
37
    apr_int64_t t;
38
    apr_time_exp_t xt;
39
    apr_int64_t t_validate; /* please see comments in cached_explode() */
40
};
41
42
/* the "+ 1" is for the current second: */
43
#define TIME_CACHE_SIZE (AP_TIME_RECENT_THRESHOLD + 1)
44
45
/* Note that AP_TIME_RECENT_THRESHOLD is defined to
46
 * be a power of two minus one in util_time.h, so that
47
 * we can replace a modulo operation with a bitwise AND
48
 * when hashing items into a cache of size
49
 * AP_TIME_RECENT_THRESHOLD+1
50
 */
51
0
#define TIME_CACHE_MASK (AP_TIME_RECENT_THRESHOLD)
52
53
static struct exploded_time_cache_element exploded_cache_localtime[TIME_CACHE_SIZE];
54
static struct exploded_time_cache_element exploded_cache_gmt[TIME_CACHE_SIZE];
55
56
57
static apr_status_t cached_explode(apr_time_exp_t *xt, apr_time_t t,
58
                                   struct exploded_time_cache_element *cache,
59
                                   int use_gmt)
60
0
{
61
0
    apr_int64_t seconds = apr_time_sec(t);
62
0
    struct exploded_time_cache_element *cache_element =
63
0
        &(cache[seconds & TIME_CACHE_MASK]);
64
0
    struct exploded_time_cache_element cache_element_snapshot;
65
66
    /* The cache is implemented as a ring buffer.  Each second,
67
     * it uses a different element in the buffer.  The timestamp
68
     * in the element indicates whether the element contains the
69
     * exploded time for the current second (vs the time
70
     * 'now - AP_TIME_RECENT_THRESHOLD' seconds ago).  If the
71
     * cached value is for the current time, we use it.  Otherwise,
72
     * we compute the apr_time_exp_t and store it in this
73
     * cache element. Note that the timestamp in the cache
74
     * element is updated only after the exploded time.  Thus
75
     * if two threads hit this cache element simultaneously
76
     * at the start of a new second, they'll both explode the
77
     * time and store it.  I.e., the writers will collide, but
78
     * they'll be writing the same value.
79
     */
80
0
    if (cache_element->t >= seconds) {
81
        /* There is an intentional race condition in this design:
82
         * in a multithreaded app, one thread might be reading
83
         * from this cache_element to resolve a timestamp from
84
         * TIME_CACHE_SIZE seconds ago at the same time that
85
         * another thread is copying the exploded form of the
86
         * current time into the same cache_element.  (I.e., the
87
         * first thread might hit this element of the ring buffer
88
         * just as the element is being recycled.)  This can
89
         * also happen at the start of a new second, if a
90
         * reader accesses the cache_element after a writer
91
         * has updated cache_element.t but before the writer
92
         * has finished updating the whole cache_element.
93
         *
94
         * Rather than trying to prevent this race condition
95
         * with locks, we allow it to happen and then detect
96
         * and correct it.  The detection works like this:
97
         *   Step 1: Take a "snapshot" of the cache element by
98
         *           copying it into a temporary buffer.
99
         *   Step 2: Check whether the snapshot contains consistent
100
         *           data: the timestamps at the start and end of
101
         *           the cache_element should both match the 'seconds'
102
         *           value that we computed from the input time.
103
         *           If these three don't match, then the snapshot
104
         *           shows the cache_element in the middle of an
105
         *           update, and its contents are invalid.
106
         *   Step 3: If the snapshot is valid, use it.  Otherwise,
107
         *           just give up on the cache and explode the
108
         *           input time.
109
         */
110
0
        memcpy(&cache_element_snapshot, cache_element,
111
0
               sizeof(struct exploded_time_cache_element));
112
0
        if ((seconds != cache_element_snapshot.t) ||
113
0
            (seconds != cache_element_snapshot.t_validate)) {
114
            /* Invalid snapshot */
115
0
            if (use_gmt) {
116
0
                return apr_time_exp_gmt(xt, t);
117
0
            }
118
0
            else {
119
0
                return apr_time_exp_lt(xt, t);
120
0
            }
121
0
        }
122
0
        else {
123
            /* Valid snapshot */
124
0
            memcpy(xt, &(cache_element_snapshot.xt),
125
0
                   sizeof(apr_time_exp_t));
126
0
        }
127
0
    }
128
0
    else {
129
0
        apr_status_t r;
130
0
        if (use_gmt) {
131
0
            r = apr_time_exp_gmt(xt, t);
132
0
        }
133
0
        else {
134
0
            r = apr_time_exp_lt(xt, t);
135
0
        }
136
0
        if (r != APR_SUCCESS) {
137
0
            return r;
138
0
        }
139
0
        cache_element->t = seconds;
140
0
        memcpy(&(cache_element->xt), xt, sizeof(apr_time_exp_t));
141
0
        cache_element->t_validate = seconds;
142
0
    }
143
0
    xt->tm_usec = (int)apr_time_usec(t);
144
0
    return APR_SUCCESS;
145
0
}
146
147
148
AP_DECLARE(apr_status_t) ap_explode_recent_localtime(apr_time_exp_t * tm,
149
                                                     apr_time_t t)
150
0
{
151
0
    return cached_explode(tm, t, exploded_cache_localtime, 0);
152
0
}
153
154
AP_DECLARE(apr_status_t) ap_explode_recent_gmt(apr_time_exp_t * tm,
155
                                               apr_time_t t)
156
0
{
157
0
    return cached_explode(tm, t, exploded_cache_gmt, 1);
158
0
}
159
160
AP_DECLARE(apr_status_t) ap_recent_ctime(char *date_str, apr_time_t t)
161
0
{
162
0
    int len = APR_CTIME_LEN;
163
0
    return ap_recent_ctime_ex(date_str, t, AP_CTIME_OPTION_NONE, &len);
164
0
}
165
166
AP_DECLARE(apr_status_t) ap_recent_ctime_ex(char *date_str, apr_time_t t,
167
                                            int option, int *len)
168
0
{
169
    /* ### This code is a clone of apr_ctime(), except that it
170
     * uses ap_explode_recent_localtime() instead of apr_time_exp_lt().
171
     */
172
0
    apr_time_exp_t xt;
173
0
    const char *s;
174
0
    int real_year;
175
0
    int needed;
176
177
178
    /* Calculate the needed buffer length */
179
0
    if (option & AP_CTIME_OPTION_COMPACT)
180
0
        needed = AP_CTIME_COMPACT_LEN;
181
0
    else
182
0
        needed = APR_CTIME_LEN;
183
184
0
    if (option & AP_CTIME_OPTION_USEC) {
185
0
        needed += AP_CTIME_USEC_LENGTH;
186
0
    }
187
188
0
    if (option & AP_CTIME_OPTION_GMTOFF) {
189
0
        needed += AP_CTIME_GMTOFF_LEN;
190
0
    }
191
192
    /* Check the provided buffer length (note: above AP_CTIME_COMPACT_LEN
193
     * and APR_CTIME_LEN include the trailing '\0'; so does 'needed' then).
194
     */
195
0
    if (len && *len >= needed) {
196
0
        *len = needed;
197
0
    }
198
0
    else {
199
0
        if (len != NULL) {
200
0
            *len = 0;
201
0
        }
202
0
        return APR_ENOMEM;
203
0
    }
204
205
    /* example without options: "Wed Jun 30 21:49:08 1993" */
206
    /* example for compact format: "1993-06-30 21:49:08" */
207
    /* example for compact+usec+gmtoff format:
208
     *     "1993-06-30 22:49:08.123456 +0100"
209
     */
210
211
0
    ap_explode_recent_localtime(&xt, t);
212
0
    real_year = 1900 + xt.tm_year;
213
0
    if (option & AP_CTIME_OPTION_COMPACT) {
214
0
        int real_month = xt.tm_mon + 1;
215
0
        *date_str++ = real_year / 1000 + '0';
216
0
        *date_str++ = real_year % 1000 / 100 + '0';
217
0
        *date_str++ = real_year % 100 / 10 + '0';
218
0
        *date_str++ = real_year % 10 + '0';
219
0
        *date_str++ = '-';
220
0
        *date_str++ = real_month / 10 + '0';
221
0
        *date_str++ = real_month % 10 + '0';
222
0
        *date_str++ = '-';
223
0
    }
224
0
    else {
225
0
        s = &apr_day_snames[xt.tm_wday][0];
226
0
        *date_str++ = *s++;
227
0
        *date_str++ = *s++;
228
0
        *date_str++ = *s++;
229
0
        *date_str++ = ' ';
230
0
        s = &apr_month_snames[xt.tm_mon][0];
231
0
        *date_str++ = *s++;
232
0
        *date_str++ = *s++;
233
0
        *date_str++ = *s++;
234
0
        *date_str++ = ' ';
235
0
    }
236
0
    *date_str++ = xt.tm_mday / 10 + '0';
237
0
    *date_str++ = xt.tm_mday % 10 + '0';
238
0
    *date_str++ = ' ';
239
0
    *date_str++ = xt.tm_hour / 10 + '0';
240
0
    *date_str++ = xt.tm_hour % 10 + '0';
241
0
    *date_str++ = ':';
242
0
    *date_str++ = xt.tm_min / 10 + '0';
243
0
    *date_str++ = xt.tm_min % 10 + '0';
244
0
    *date_str++ = ':';
245
0
    *date_str++ = xt.tm_sec / 10 + '0';
246
0
    *date_str++ = xt.tm_sec % 10 + '0';
247
0
    if (option & AP_CTIME_OPTION_USEC) {
248
0
        int div;
249
0
        int usec = (int)xt.tm_usec;
250
0
        *date_str++ = '.';
251
0
        for (div=100000; div>0; div=div/10) {
252
0
            *date_str++ = usec / div + '0';
253
0
            usec = usec % div;
254
0
        }
255
0
    }
256
0
    if (!(option & AP_CTIME_OPTION_COMPACT)) {
257
0
        *date_str++ = ' ';
258
0
        *date_str++ = real_year / 1000 + '0';
259
0
        *date_str++ = real_year % 1000 / 100 + '0';
260
0
        *date_str++ = real_year % 100 / 10 + '0';
261
0
        *date_str++ = real_year % 10 + '0';
262
0
    }
263
0
    if (option & AP_CTIME_OPTION_GMTOFF) {
264
0
        int off = xt.tm_gmtoff, off_hh, off_mm;
265
0
        char sign = '+';
266
0
        if (off < 0) {
267
0
            off = -off;
268
0
            sign = '-';
269
0
        }
270
0
        off_hh = off / 3600;
271
0
        off_mm = off % 3600 / 60;
272
0
        *date_str++ = ' ';
273
0
        *date_str++ = sign;
274
0
        *date_str++ = off_hh / 10 + '0';
275
0
        *date_str++ = off_hh % 10 + '0';
276
0
        *date_str++ = off_mm / 10 + '0';
277
0
        *date_str++ = off_mm % 10 + '0';
278
0
    }
279
0
    *date_str = 0;
280
281
0
    return APR_SUCCESS;
282
0
}
283
284
AP_DECLARE(apr_status_t) ap_recent_rfc822_date(char *date_str, apr_time_t t)
285
0
{
286
    /* ### This code is a clone of apr_rfc822_date(), except that it
287
     * uses ap_explode_recent_gmt() instead of apr_time_exp_gmt().
288
     */
289
0
    apr_time_exp_t xt;
290
0
    const char *s;
291
0
    int real_year;
292
293
0
    ap_explode_recent_gmt(&xt, t);
294
295
    /* example: "Sat, 08 Jan 2000 18:31:41 GMT" */
296
    /*           12345678901234567890123456789  */
297
298
0
    s = &apr_day_snames[xt.tm_wday][0];
299
0
    *date_str++ = *s++;
300
0
    *date_str++ = *s++;
301
0
    *date_str++ = *s++;
302
0
    *date_str++ = ',';
303
0
    *date_str++ = ' ';
304
0
    *date_str++ = xt.tm_mday / 10 + '0';
305
0
    *date_str++ = xt.tm_mday % 10 + '0';
306
0
    *date_str++ = ' ';
307
0
    s = &apr_month_snames[xt.tm_mon][0];
308
0
    *date_str++ = *s++;
309
0
    *date_str++ = *s++;
310
0
    *date_str++ = *s++;
311
0
    *date_str++ = ' ';
312
0
    real_year = 1900 + xt.tm_year;
313
    /* This routine isn't y10k ready. */
314
0
    *date_str++ = real_year / 1000 + '0';
315
0
    *date_str++ = real_year % 1000 / 100 + '0';
316
0
    *date_str++ = real_year % 100 / 10 + '0';
317
0
    *date_str++ = real_year % 10 + '0';
318
0
    *date_str++ = ' ';
319
0
    *date_str++ = xt.tm_hour / 10 + '0';
320
0
    *date_str++ = xt.tm_hour % 10 + '0';
321
0
    *date_str++ = ':';
322
0
    *date_str++ = xt.tm_min / 10 + '0';
323
0
    *date_str++ = xt.tm_min % 10 + '0';
324
0
    *date_str++ = ':';
325
0
    *date_str++ = xt.tm_sec / 10 + '0';
326
0
    *date_str++ = xt.tm_sec % 10 + '0';
327
0
    *date_str++ = ' ';
328
0
    *date_str++ = 'G';
329
0
    *date_str++ = 'M';
330
0
    *date_str++ = 'T';
331
0
    *date_str++ = 0;
332
0
    return APR_SUCCESS;
333
0
}
334
335
0
AP_DECLARE(void) ap_force_set_tz(apr_pool_t *p) {
336
    /* If the TZ variable is unset, many operating systems,
337
     * such as Linux, will at runtime read from /etc/localtime
338
     * and call fstat on it.
339
     *
340
     * By forcing the time zone to UTC if it is unset, we gain
341
     * about 2% in raw requests/second (since we format log files
342
     * in the local time, if present)
343
     *
344
     * For more info, see:
345
     *   <http://www.gnu.org/s/hello/manual/libc/TZ-Variable.html>
346
     */
347
0
    char *v = NULL;
348
349
0
    if (apr_env_get(&v, "TZ", p) != APR_SUCCESS) {
350
0
        apr_env_set("TZ", "UTC+0", p);
351
0
    }
352
0
}