Skip to content

Commit 8e67370

Browse files
derrickstoleegitster
authored andcommitted
trace2: tolerate failed timestamp formatting
Some users reported issues of repeated messages: fatal: recursion detected in die handler This wasn't happening every time, but we eventually captured a GIT_TRACE2_PERF log file with this issue and revealed an interesting internal detail, failing with this message: unable to format message: %4d-%02d-%02dT%02d:%02d:%02d.%06ldZ This specific format string tracks to tr2_tbuf_utc_datetime_extended() in trace2/tr2_tbuf.c. This logic began as tr2_tbuf_utc_time() in ee4512e (trace2: create new combined trace facility, 2019-02-22) but was later split in bad229a (trace2: clarify UTC datetime formatting, 2019-04-15). This use of xsnprintf() is writing a very specific datetime format into a 32-character buffer. The format requires that the input data will not overflow the format digits or the buffer will not hold the result. Since we are using xsnprintf() here, those failures turn into die() events. This method and its siblings, tr2_tbuf_local_time() and tr2_tbuf_utc_datetime(), are used in the tracing library. The extended form is used only for the 'event' format, which these users were using via a config setting for use in client-side telemetry. The non-extended form is used to help generate the 'SID' that defines the process in the traces. Not only are these inappropriate times for a failure, but the extended method is called specifially during the 'atexit' event, which was triggering this problem in a loop as the 'atexit' event would be retriggered by the die(). I could not determine the exact cause of why these errors started occuring in a bunch. My best guess is that these users are dogfooding an early operating system version that is more likely to fail in the gettimeofday() function and thus leaves the structures uninitialized and potentially violating the expected values. However, for full defense-in-depth I made several modifications: 1. Both 'tv' and 'tm' structs are initialized with zero values, allowing an erroring gettimeofday() or gmtime_r() method to leave them zero-valued. A zero-valued date is better than a die() here. 2. Replace the use of xsnprintf() with snprintf() to avoid the possibility of calling die() here. Instead, check the response to see if there was a failure. On failure, put a blank value into the buffer instead of possibly allowing a value that would not format correctly for a trace2 consumer. This value should be seen as obviously wrong and therefore signals a problem. As the core issue in this code seems to require a system method returning an error, no test accompanies this change. This change removes all uses of xsnprintf() from the trace2/ directory. There are two uses of xstrdup() that could be considered for removal, but they only die() on out-of-memory errors instead of formatting issues. I chose to leave those in place for now. Signed-off-by: Derrick Stolee <stolee@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
1 parent 55526a1 commit 8e67370

1 file changed

Lines changed: 34 additions & 15 deletions

File tree

trace2/tr2_tbuf.c

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,45 +3,64 @@
33

44
void tr2_tbuf_local_time(struct tr2_tbuf *tb)
55
{
6-
struct timeval tv;
7-
struct tm tm;
6+
struct timeval tv = { 0 };
7+
struct tm tm = { 0 };
88
time_t secs;
9+
int len;
910

1011
gettimeofday(&tv, NULL);
1112
secs = tv.tv_sec;
1213
localtime_r(&secs, &tm);
1314

14-
xsnprintf(tb->buf, sizeof(tb->buf), "%02d:%02d:%02d.%06ld", tm.tm_hour,
15-
tm.tm_min, tm.tm_sec, (long)tv.tv_usec);
15+
len = snprintf(tb->buf, sizeof(tb->buf), "%02d:%02d:%02d.%06ld",
16+
tm.tm_hour, tm.tm_min, tm.tm_sec, (long)tv.tv_usec);
17+
18+
if (len < 0 || (size_t)len >= sizeof(tb->buf)) {
19+
const char *blank = "00:00:00.000000";
20+
strlcpy(tb->buf, blank, sizeof(tb->buf));
21+
}
1622
}
1723

1824
void tr2_tbuf_utc_datetime_extended(struct tr2_tbuf *tb)
1925
{
20-
struct timeval tv;
21-
struct tm tm;
26+
struct timeval tv = { 0 };
27+
struct tm tm = { 0 };
2228
time_t secs;
29+
int len;
2330

2431
gettimeofday(&tv, NULL);
2532
secs = tv.tv_sec;
2633
gmtime_r(&secs, &tm);
2734

28-
xsnprintf(tb->buf, sizeof(tb->buf),
29-
"%4d-%02d-%02dT%02d:%02d:%02d.%06ldZ", tm.tm_year + 1900,
30-
tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec,
31-
(long)tv.tv_usec);
35+
len = snprintf(tb->buf, sizeof(tb->buf),
36+
"%4d-%02d-%02dT%02d:%02d:%02d.%06ldZ",
37+
tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
38+
tm.tm_hour, tm.tm_min, tm.tm_sec, (long)tv.tv_usec);
39+
40+
if (len < 0 || (size_t)len >= sizeof(tb->buf)) {
41+
const char *blank = "1900-00-00T00:00:00.000000Z";
42+
strlcpy(tb->buf, blank, sizeof(tb->buf));
43+
}
3244
}
3345

3446
void tr2_tbuf_utc_datetime(struct tr2_tbuf *tb)
3547
{
36-
struct timeval tv;
37-
struct tm tm;
48+
struct timeval tv = { 0 };
49+
struct tm tm = { 0 };
3850
time_t secs;
51+
int len;
3952

4053
gettimeofday(&tv, NULL);
4154
secs = tv.tv_sec;
4255
gmtime_r(&secs, &tm);
4356

44-
xsnprintf(tb->buf, sizeof(tb->buf), "%4d%02d%02dT%02d%02d%02d.%06ldZ",
45-
tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour,
46-
tm.tm_min, tm.tm_sec, (long)tv.tv_usec);
57+
len = snprintf(tb->buf, sizeof(tb->buf),
58+
"%4d%02d%02dT%02d%02d%02d.%06ldZ",
59+
tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
60+
tm.tm_hour, tm.tm_min, tm.tm_sec, (long)tv.tv_usec);
61+
62+
if (len < 0 || (size_t)len >= sizeof(tb->buf)) {
63+
const char *blank = "19000000T000000.000000Z";
64+
strlcpy(tb->buf, blank, sizeof(tb->buf));
65+
}
4766
}

0 commit comments

Comments
 (0)