From 759f263d600437093452ede20899a82cebd0cef8 Mon Sep 17 00:00:00 2001 From: Ben Harris Date: Tue, 4 Feb 2025 23:11:17 +0000 Subject: [PATCH] More portable handling of SOURCE_DATE_EPOCH Now we don't make any assumption about being on a POSIX system, and instead have our own implementation of gmtime(). This turns out to be shorter than the comment explaining why the previous code was more or less valid. While I'm in the area, also fail if the year is after 9999, since asctime() causes undefined behaviour in those cases. --- bedstead.c | 95 +++++++++++++++++++++++------------------------------- 1 file changed, 40 insertions(+), 55 deletions(-) diff --git a/bedstead.c b/bedstead.c index fbeee78..9bdc44d 100644 --- a/bedstead.c +++ b/bedstead.c @@ -3144,79 +3144,64 @@ fullname_to_fontname(char const *fullname) static char * time_for_ttx(void) { - time_t now = 0; + struct tm *timeptr, tm; uintmax_t epochumax; - struct tm *timeptr; char *epochstr, *endptr, *timestr; /* Work out what timestamp to use. */ if ((epochstr = getenv("SOURCE_DATE_EPOCH")) != NULL) { - /* - * Correctly handling SOURCE_DATE_EPOCH is - * surprisingly fiddly. To make life slightly easy, - * we assume that we're on a POSIX system, where - * time_t is an integer type, and that we can reject - * negative values out of hand. - * - * Even given that, time_t might be any integer type. - * It might be signed or unsigned, and it might be a - * standard or an extended type. - * - * If time_t is unsigned, it's always safe to convert - * something to it. If the value is within the range - * of time_t it will remain unchanged, and if it - * isn't, it'll be reduced to be within the range (C11 - * 6.3.1.3). - * - * While it's well-known that signed integer overflow - * in C causes undefined behaviour, this doesn't apply - * to conversion. According to C11 6.3.1.3, if a - * conversion overflows "either the result is - * implementation-defined or an implementation-defined - * signal is raised." I can't find any clear - * definition of what might happen when such a signal - * is raised, but I think it must either cause the - * process to exit with an error or do nothing (and - * presumably leave the destination untouched). - * - * So having initialised the destination to 0, we can - * assume that after the assignment it will either - * have the correct value or have a different value - * within the range of time_t. Then we just have to - * check it. - */ errno = 0; epochumax = strtoumax(epochstr, &endptr, 10); if (!isdigit((unsigned char)epochstr[0]) || endptr == epochstr || *endptr != '\0' || - errno == ERANGE) { + errno == ERANGE || + /* Limit ourselves to 2000 to 10000. */ + epochumax < 946684800 || epochumax >= 253402300800) { fprintf(stderr, "Invalid SOURCE_DATE_EPOCH\n"); return NULL; } - now = epochumax; - /* - * Check if the value fitted into time_t. If "now" is - * negative then it obviously didn't. If it's - * non-negative then converting back into a uintmax_t - * will not change the value, since all non-negative - * numbers that can be represented in any integer type - * can be represented in a uintmax_t. - */ - if (now < 0 || (uintmax_t)now != epochumax) { - fprintf(stderr, "Invalid SOURCE_DATE_EPOCH\n"); - return NULL; + epochumax -= 946684800; /* Rebase to 2000. */ + tm.tm_isdst = -1; + tm.tm_sec = epochumax % 60; + tm.tm_min = epochumax / 60 % 60; + tm.tm_hour = epochumax / 3600 % 24; + long day = epochumax / 86400; + tm.tm_wday = (day - 1) % 7; + int y = 2000 + (day / 146097) * 400; + day = day % 146097; + bool ly; + for (;;) { + ly = y % 400 ? y % 100 ? y % 4 ? false:true:false:true; + if (day < 365 + ly) break; + day -= 365 + ly; y++; } + tm.tm_year = y - 1900; + tm.tm_yday = day; + static int const mlc[] = {31,28,31,30,31,30,31,31,30,31,30,31}; + static int const mll[] = {31,29,31,30,31,30,31,31,30,31,30,31}; + tm.tm_mon = 0; + for (;;) { + int md = (ly ? mll : mlc)[tm.tm_mon]; + if (day < md) break; + day -= md; tm.tm_mon++; + } + tm.tm_mday = day + 1; + timeptr = &tm; } else { - now = time(NULL); + time_t now = time(NULL); if (now == (time_t)-1) { fprintf(stderr, "Can't get current time\n"); return NULL; } - } - timeptr = gmtime(&now); - if (timeptr == NULL) { - fprintf(stderr, "Can't convert time to UTC\n"); - return NULL; + timeptr = gmtime(&now); + if (timeptr == NULL) { + fprintf(stderr, "Can't convert time to UTC\n"); + return NULL; + } + if (timeptr->tm_year >= 8100) { + fprintf(stderr, "Can't handle years past 9999\n"); + return NULL; + } } timestr = asctime(timeptr); assert(strlen(timestr) == 25); -- 2.30.2