* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
+/* Copyright (c) The Exim Maintainers 2020 */
/* See the file NOTICE for conditions of use and distribution. */
/* A function for returning the time of day in various formats */
/* #define TESTING_LOG_DATESTAMP */
-static uschar timebuf[sizeof("www, dd-mmm-yyyy hh:mm:ss +zzzz")];
+static uschar timebuf[sizeof("www, dd-mmm-yyyy hh:mm:ss.ddd +zzzz")];
/*************************************************
Argument: type of timestamp required:
tod_bsdin BSD inbox format
tod_epoch Unix epoch format
+ tod_epochl Unix epoch/usec format
tod_full full date and time
tod_log log file data line format,
with zone if log_timezone is TRUE
uschar *
tod_stamp(int type)
{
-time_t now = time(NULL);
-struct tm *t;
+struct timeval now;
+struct tm * t;
-/* Vary log type according to timezone requirement */
-
-if (type == tod_log) type = log_timezone? tod_log_zone : tod_log_bare;
+gettimeofday(&now, NULL);
/* Styles that don't need local time */
-else if (type == tod_epoch)
+switch(type)
{
- (void) sprintf(CS timebuf, "%d", (int)now); /* Unix epoch format */
- return timebuf;
+ case tod_epoch:
+ (void) snprintf(CS timebuf, sizeof(timebuf), TIME_T_FMT, now.tv_sec); /* Unix epoch format */
+ return timebuf; /* NB the above will be wrong if time_t is FP */
+
+ case tod_epoch_l:
+ /* Unix epoch/usec format */
+ (void) snprintf(CS timebuf, sizeof(timebuf), TIME_T_FMT "%06ld", now.tv_sec, (long) now.tv_usec );
+ return timebuf;
+
+ case tod_zulu:
+ t = gmtime(&now.tv_sec);
+ (void) snprintf(CS timebuf, sizeof(timebuf), "%04u%02u%02u%02u%02u%02uZ",
+ 1900 + (uint)t->tm_year, 1 + (uint)t->tm_mon, (uint)t->tm_mday, (uint)t->tm_hour, (uint)t->tm_min,
+ (uint)t->tm_sec);
+ return timebuf;
}
-else if (type == tod_zulu)
- {
- t = gmtime(&now);
- (void) sprintf(CS timebuf, "%04d%02d%02d%02d%02d%02dZ",
- 1900 + t->tm_year, 1 + t->tm_mon, t->tm_mday, t->tm_hour, t->tm_min,
- t->tm_sec);
- return timebuf;
- }
+/* Vary log type according to timezone requirement */
+
+if (type == tod_log) type = log_timezone ? tod_log_zone : tod_log_bare;
/* Convert to local time or UTC */
-t = timestamps_utc? gmtime(&now) : localtime(&now);
+t = f.timestamps_utc ? gmtime(&now.tv_sec) : localtime(&now.tv_sec);
switch(type)
{
case tod_log_bare: /* Format used in logging without timezone */
- (void) sprintf(CS timebuf, "%04d-%02d-%02d %02d:%02d:%02d",
- 1900 + t->tm_year, 1 + t->tm_mon, t->tm_mday,
- t->tm_hour, t->tm_min, t->tm_sec);
- break;
+#ifndef COMPILE_UTILITY
+ if (LOGGING(millisec))
+ snprintf(CS timebuf, sizeof(timebuf), "%04u-%02u-%02u %02u:%02u:%02u.%03u",
+ 1900 + (uint)t->tm_year, 1 + (uint)t->tm_mon, (uint)t->tm_mday,
+ (uint)t->tm_hour, (uint)t->tm_min, (uint)t->tm_sec,
+ (uint)(now.tv_usec/1000));
+ else
+#endif
+ snprintf(CS timebuf, sizeof(timebuf), "%04u-%02u-%02u %02u:%02u:%02u",
+ 1900 + (uint)t->tm_year, 1 + (uint)t->tm_mon, (uint)t->tm_mday,
+ (uint)t->tm_hour, (uint)t->tm_min, (uint)t->tm_sec);
- /* Format used as suffix of log file name when 'log_datestamp' is active. For
- testing purposes, it changes the file every second. */
+ break;
- #ifdef TESTING_LOG_DATESTAMP
+ /* Format used as suffix of log file name when 'log_datestamp' is active. For
+ testing purposes, it changes the file every second. */
+
+#ifdef TESTING_LOG_DATESTAMP
case tod_log_datestamp_daily:
case tod_log_datestamp_monthly:
- (void) sprintf(CS timebuf, "%04d%02d%02d%02d%02d",
- 1900 + t->tm_year, 1 + t->tm_mon, t->tm_mday, t->tm_hour, t->tm_min);
- break;
+ snprintf(CS timebuf, sizeof(timebuf), "%04u%02u%02u%02u%02u",
+ 1900 + (uint)t->tm_year, 1 + (uint)t->tm_mon, (uint)t->tm_mday,
+ (uint)t->tm_hour, (uint)t->tm_min);
+ break;
- #else
+#else
case tod_log_datestamp_daily:
- (void) sprintf(CS timebuf, "%04d%02d%02d",
- 1900 + t->tm_year, 1 + t->tm_mon, t->tm_mday);
- break;
+ snprintf(CS timebuf, sizeof(timebuf), "%04u%02u%02u",
+ 1900 + (uint)t->tm_year, 1 + (uint)t->tm_mon, (uint)t->tm_mday);
+ break;
case tod_log_datestamp_monthly:
- (void) sprintf(CS timebuf, "%04d%02d",
- 1900 + t->tm_year, 1 + t->tm_mon);
- break;
- #endif
-
- /* Format used in BSD inbox separator lines. Sort-of documented in RFC 976
- ("UUCP Mail Interchange Format Standard") but only by example, not by
- explicit definition. The examples show no timezone offsets, and some MUAs
- appear to be sensitive to this, so Exim has been changed to remove the
- timezone offsets that originally appeared. */
+#ifndef COMPILE_UTILITY
+ snprintf(CS timebuf, sizeof(timebuf), "%04u%02u",
+ 1900 + (uint)t->tm_year, 1 + (uint)t->tm_mon);
+#endif
+ break;
+#endif
+
+ /* Format used in BSD inbox separator lines. Sort-of documented in RFC 976
+ ("UUCP Mail Interchange Format Standard") but only by example, not by
+ explicit definition. The examples show no timezone offsets, and some MUAs
+ appear to be sensitive to this, so Exim has been changed to remove the
+ timezone offsets that originally appeared. */
case tod_bsdin:
- {
- int len = Ustrftime(timebuf, sizeof(timebuf), "%a %b %d %H:%M:%S", t);
- Ustrftime(timebuf + len, sizeof(timebuf) - len, " %Y", t);
- }
- break;
-
- /* Other types require the GMT offset to be calculated, or just set up in the
- case of UTC timestamping. We need to take a copy of the local time first. */
-
- default:
- {
- int diff_hour, diff_min;
- struct tm local;
- memcpy(&local, t, sizeof(struct tm));
-
- if (timestamps_utc)
{
- diff_hour = diff_min = 0;
- }
- else
- {
- struct tm *gmt = gmtime(&now);
- diff_min = 60*(local.tm_hour - gmt->tm_hour) + local.tm_min - gmt->tm_min;
- if (local.tm_year != gmt->tm_year)
- diff_min += (local.tm_year > gmt->tm_year)? 1440 : -1440;
- else if (local.tm_yday != gmt->tm_yday)
- diff_min += (local.tm_yday > gmt->tm_yday)? 1440 : -1440;
- diff_hour = diff_min/60;
- diff_min = abs(diff_min - diff_hour*60);
+ int len = Ustrftime(timebuf, sizeof(timebuf), "%a %b %d %H:%M:%S", t);
+ Ustrftime(timebuf + len, sizeof(timebuf) - len, " %Y", t);
}
+ break;
- switch(type)
+ /* Other types require the GMT offset to be calculated, or just set up in the
+ case of UTC timestamping. We need to take a copy of the local time first. */
+
+ default:
{
- case tod_log_zone: /* Format used in logging with timezone */
- (void) sprintf(CS timebuf, "%04d-%02d-%02d %02d:%02d:%02d %+03d%02d",
- 1900 + local.tm_year, 1 + local.tm_mon, local.tm_mday,
- local.tm_hour, local.tm_min, local.tm_sec,
- diff_hour, diff_min);
- break;
-
- case tod_zone: /* Just the timezone offset */
- (void) sprintf(CS timebuf, "%+03d%02d", diff_hour, diff_min);
- break;
-
- /* tod_mbx: format used in MBX mailboxes - subtly different to tod_full */
-
- #ifdef SUPPORT_MBX
- case tod_mbx:
- {
- int len;
- (void) sprintf(CS timebuf, "%02d-", local.tm_mday);
- len = Ustrlen(timebuf);
- len += Ustrftime(timebuf + len, sizeof(timebuf) - len, "%b-%Y %H:%M:%S",
- &local);
- (void) sprintf(CS timebuf + len, " %+03d%02d", diff_hour, diff_min);
- }
- break;
- #endif
-
- /* tod_full: format used in Received: headers (use as default just in case
- called with a junk type value) */
-
- default:
- {
- int len = Ustrftime(timebuf, sizeof(timebuf), "%a, ", &local);
- (void) sprintf(CS timebuf + len, "%02d ", local.tm_mday);
- len += Ustrlen(timebuf + len);
- len += Ustrftime(timebuf + len, sizeof(timebuf) - len, "%b %Y %H:%M:%S",
- &local);
- (void) sprintf(CS timebuf + len, " %+03d%02d", diff_hour, diff_min);
- }
- break;
+ int diff_hour, diff_min;
+ struct tm local;
+ struct tm * lp = &local;
+ memcpy(lp, t, sizeof(struct tm));
+
+ if (f.timestamps_utc)
+ diff_hour = diff_min = 0;
+ else
+ {
+ struct tm * gmt = gmtime(&now.tv_sec);
+
+ if (local.tm_sec == gmt->tm_sec) /* usual case */
+ {
+ diff_min = 60 * (local.tm_hour - gmt->tm_hour)
+ + local.tm_min - gmt->tm_min;
+ if (local.tm_year != gmt->tm_year)
+ diff_min += (local.tm_year > gmt->tm_year) ? 1440 : -1440;
+ else if (local.tm_yday != gmt->tm_yday)
+ diff_min += (local.tm_yday > gmt->tm_yday) ? 1440 : -1440;
+ diff_hour = diff_min/60;
+ diff_min = abs(diff_min - diff_hour*60);
+ }
+ else /* subminute offset, eg. TAI */
+ {
+ lp = gmt; /* pretend we're in UTC */
+ diff_min = diff_hour = 0;
+ }
+ }
+
+ switch(type)
+ {
+ case tod_log_zone: /* Format used in logging with timezone */
+#ifndef COMPILE_UTILITY
+ if (LOGGING(millisec))
+ (void) snprintf(CS timebuf, sizeof(timebuf),
+ "%04u-%02u-%02u %02u:%02u:%02u.%03u %+03d%02d",
+ 1900 + (uint)lp->tm_year, 1 + (uint)lp->tm_mon, (uint)lp->tm_mday,
+ (uint)lp->tm_hour, (uint)lp->tm_min, (uint)lp->tm_sec, (uint)(now.tv_usec/1000),
+ diff_hour, diff_min);
+ else
+#endif
+ (void) snprintf(CS timebuf, sizeof(timebuf),
+ "%04u-%02u-%02u %02u:%02u:%02u %+03d%02d",
+ 1900 + (uint)lp->tm_year, 1 + (uint)lp->tm_mon, (uint)lp->tm_mday,
+ (uint)lp->tm_hour, (uint)lp->tm_min, (uint)lp->tm_sec,
+ diff_hour, diff_min);
+ break;
+
+ case tod_zone: /* Just the timezone offset */
+ (void) snprintf(CS timebuf, sizeof(timebuf), "%+03d%02d", diff_hour, diff_min);
+ break;
+
+ /* tod_mbx: format used in MBX mailboxes - subtly different to tod_full */
+
+#ifdef SUPPORT_MBX
+ case tod_mbx:
+ {
+ int len = snprintf(CS timebuf, sizeof(timebuf),
+ "%02u-", (uint)lp->tm_mday);
+ len += Ustrftime(timebuf + len, sizeof(timebuf) - len,
+ "%b-%Y %H:%M:%S", lp);
+ (void) snprintf(CS timebuf + len, sizeof(timebuf)-len,
+ " %+03d%02d", diff_hour, diff_min);
+ }
+ break;
+#endif
+
+ /* tod_full: format used in Received: headers (use as default just in case
+ called with a junk type value) */
+
+ default:
+ {
+ int len = Ustrftime(timebuf, sizeof(timebuf), "%a, ", lp);
+ len += snprintf(CS timebuf + len, sizeof(timebuf)-len,
+ "%02u ", (uint)lp->tm_mday);
+ len += Ustrftime(timebuf + len, sizeof(timebuf) - len,
+ "%b %Y %H:%M:%S", lp);
+ (void) snprintf(CS timebuf + len, sizeof(timebuf)-len,
+ " %+03d%02d", diff_hour, diff_min);
+ }
+ break;
+ }
}
- }
- break;
+ break;
}
return timebuf;