From 65198ff81545bd146511511eda534c699cb100b7 Mon Sep 17 00:00:00 2001 From: Benoit GARNIER <chezbunch+hapr...@gmail.com> Date: Sun, 27 Mar 2016 03:04:16 +0200 Subject: [PATCH] BUG/MINOR: log: Don't use strftime() which can clobber timezone if chrooted
The strftime() function can call tzset() internally on some platforms. When haproxy is chrooted, the /etc/localtime file is not found, and some implementations will clobber the content of the current timezone. The GMT offset is computed by diffing the times returned by gmtime_r() and localtime_r(). These variants are guaranteed to not call tzset() and were already used in haproxy while chrooted, so they should be safe. This patch must be backported to 1.6 and 1.5. --- include/common/standard.h | 6 ++-- src/log.c | 4 +-- src/standard.c | 76 ++++++++++++++++++++++++++++++++++------------- 3 files changed, 62 insertions(+), 24 deletions(-) diff --git a/include/common/standard.h b/include/common/standard.h index 353d0b0..cd2208c 100644 --- a/include/common/standard.h +++ b/include/common/standard.h @@ -871,10 +871,11 @@ extern const char *monthname[]; char *date2str_log(char *dest, struct tm *tm, struct timeval *date, size_t size); /* Return the GMT offset for a specific local time. + * Both t and tm must represent the same time. * The string returned has the same format as returned by strftime(... "%z", tm). * Offsets are kept in an internal cache for better performances. */ -const char *get_gmt_offset(struct tm *tm); +const char *get_gmt_offset(time_t t, struct tm *tm); /* gmt2str_log: write a date in the format : * "%02d/%s/%04d:%02d:%02d:%02d +0000" without using snprintf @@ -885,10 +886,11 @@ char *gmt2str_log(char *dst, struct tm *tm, size_t size); /* localdate2str_log: write a date in the format : * "%02d/%s/%04d:%02d:%02d:%02d +0000(local timezone)" without using snprintf + * Both t and tm must represent the same time. * return a pointer to the last char written (\0) or * NULL if there isn't enough space. */ -char *localdate2str_log(char *dst, struct tm *tm, size_t size); +char *localdate2str_log(char *dst, time_t t, struct tm *tm, size_t size); /* These 3 functions parses date string and fills the * corresponding broken-down time in <tm>. In succes case, diff --git a/src/log.c b/src/log.c index ab38353..4d496cd 100644 --- a/src/log.c +++ b/src/log.c @@ -979,7 +979,7 @@ static char *update_log_hdr_rfc5424(const time_t time) tvsec = time; get_localtime(tvsec, &tm); - gmt_offset = get_gmt_offset(&tm); + gmt_offset = get_gmt_offset(time, &tm); hdr_len = snprintf(logheader_rfc5424, global.max_syslog_len, "<<<<>1 %4d-%02d-%02dT%02d:%02d:%02d%.3s:%.2s %s ", @@ -1495,7 +1495,7 @@ int build_logline(struct stream *s, char *dst, size_t maxsize, struct list *list case LOG_FMT_DATELOCAL: // %Tl get_localtime(s->logs.accept_date.tv_sec, &tm); - ret = localdate2str_log(tmplog, &tm, dst + maxsize - tmplog); + ret = localdate2str_log(tmplog, s->logs.accept_date.tv_sec, &tm, dst + maxsize - tmplog); if (ret == NULL) goto out; tmplog = ret; diff --git a/src/standard.c b/src/standard.c index e08795f..2fe92ba 100644 --- a/src/standard.c +++ b/src/standard.c @@ -2552,31 +2552,66 @@ char *date2str_log(char *dst, struct tm *tm, struct timeval *date, size_t size) return dst; } +/* Base year used to compute leap years */ +#define TM_YEAR_BASE 1900 + +/* Return the difference in seconds between two times (leap seconds are ignored). + * Retrieved from glibc 2.18 source code. + */ +static int my_tm_diff(const struct tm *a, const struct tm *b) +{ + /* Compute intervening leap days correctly even if year is negative. + * Take care to avoid int overflow in leap day calculations, + * but it's OK to assume that A and B are close to each other. + */ + int a4 = (a->tm_year >> 2) + (TM_YEAR_BASE >> 2) - ! (a->tm_year & 3); + int b4 = (b->tm_year >> 2) + (TM_YEAR_BASE >> 2) - ! (b->tm_year & 3); + int a100 = a4 / 25 - (a4 % 25 < 0); + int b100 = b4 / 25 - (b4 % 25 < 0); + int a400 = a100 >> 2; + int b400 = b100 >> 2; + int intervening_leap_days = (a4 - b4) - (a100 - b100) + (a400 - b400); + int years = a->tm_year - b->tm_year; + int days = (365 * years + intervening_leap_days + + (a->tm_yday - b->tm_yday)); + return (60 * (60 * (24 * days + (a->tm_hour - b->tm_hour)) + + (a->tm_min - b->tm_min)) + + (a->tm_sec - b->tm_sec)); +} + /* Return the GMT offset for a specific local time. + * Both t and tm must represent the same time. * The string returned has the same format as returned by strftime(... "%z", tm). * Offsets are kept in an internal cache for better performances. */ -const char *get_gmt_offset(struct tm *tm) +const char *get_gmt_offset(time_t t, struct tm *tm) { /* Cache offsets from GMT (depending on whether DST is active or not) */ static char gmt_offsets[2][5+1] = { "", "" }; - int old_isdst = tm->tm_isdst; char *gmt_offset; - - /* Pretend DST not active if its status is unknown, or strftime() will return an empty string for "%z" */ - if (tm->tm_isdst < 0) { - tm->tm_isdst = 0; - } - - /* Fetch the offset and initialize it if needed */ - gmt_offset = gmt_offsets[tm->tm_isdst & 0x01]; - if (unlikely(!*gmt_offset)) { - strftime(gmt_offset, 5+1, "%z", tm); - } - - /* Restore previous DST flag */ - tm->tm_isdst = old_isdst; + struct tm tm_gmt; + int diff; + int isdst = tm->tm_isdst; + + /* Pretend DST not active if its status is unknown */ + if (isdst < 0) + isdst = 0; + + /* Fetch the offset and initialize it if needed */ + gmt_offset = gmt_offsets[isdst & 0x01]; + if (unlikely(!*gmt_offset)) { + get_gmtime(t, &tm_gmt); + diff = my_tm_diff(tm, &tm_gmt); + if (diff < 0) { + diff = -diff; + *gmt_offset = '-'; + } else { + *gmt_offset = '+'; + } + diff /= 60; /* Convert to minutes */ + snprintf(gmt_offset+1, 4+1, "%02d%02d", diff/60, diff%60); + } return gmt_offset; } @@ -2616,16 +2651,17 @@ char *gmt2str_log(char *dst, struct tm *tm, size_t size) /* localdate2str_log: write a date in the format : * "%02d/%s/%04d:%02d:%02d:%02d +0000(local timezone)" without using snprintf - * * return a pointer to the last char written (\0) or - * * NULL if there isn't enough space. + * Both t and tm must represent the same time. + * return a pointer to the last char written (\0) or + * NULL if there isn't enough space. */ -char *localdate2str_log(char *dst, struct tm *tm, size_t size) +char *localdate2str_log(char *dst, time_t t, struct tm *tm, size_t size) { const char *gmt_offset; if (size < 27) /* the size is fixed: 26 chars + \0 */ return NULL; - gmt_offset = get_gmt_offset(tm); + gmt_offset = get_gmt_offset(t, tm); dst = utoa_pad((unsigned int)tm->tm_mday, dst, 3); // day *dst++ = '/'; -- 2.5.0