mirror of
https://github.com/fluencelabs/musl
synced 2025-06-03 10:01:38 +00:00
these functions were spuriously failing in the case where the buffer size was exactly the number of bytes/characters to be written, including null termination. since these functions do not have defined error conditions other than buffer size, a reasonable application may fail to check the return value when the format string and buffer size are known to be valid; such an application could then attempt to use a non-terminated buffer. in addition to fixing the bug, I have changed the error handling behavior so that these functions always null-terminate the output except in the case where the buffer size is zero, and so that they always write as many characters as possible before failing, rather than dropping whole fields that do not fit. this actually simplifies the logic somewhat anyway.
269 lines
5.4 KiB
C
269 lines
5.4 KiB
C
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <langinfo.h>
|
|
#include <locale.h>
|
|
#include <time.h>
|
|
#include <limits.h>
|
|
#include "libc.h"
|
|
#include "time_impl.h"
|
|
|
|
const char *__nl_langinfo_l(nl_item, locale_t);
|
|
|
|
static int is_leap(int y)
|
|
{
|
|
/* Avoid overflow */
|
|
if (y>INT_MAX-1900) y -= 2000;
|
|
y += 1900;
|
|
return !(y%4) && ((y%100) || !(y%400));
|
|
}
|
|
|
|
static int week_num(const struct tm *tm)
|
|
{
|
|
int val = (tm->tm_yday + 7 - (tm->tm_wday+6)%7) / 7;
|
|
/* If 1 Jan is just 1-3 days past Monday,
|
|
* the previous week is also in this year. */
|
|
if ((tm->tm_wday - tm->tm_yday - 2 + 371) % 7 <= 2)
|
|
val++;
|
|
if (!val) {
|
|
val = 52;
|
|
/* If 31 December of prev year a Thursday,
|
|
* or Friday of a leap year, then the
|
|
* prev year has 53 weeks. */
|
|
int dec31 = (tm->tm_wday - tm->tm_yday - 1 + 7) % 7;
|
|
if (dec31 == 4 || (dec31 == 5 && is_leap(tm->tm_year%400-1)))
|
|
val++;
|
|
} else if (val == 53) {
|
|
/* If 1 January is not a Thursday, and not
|
|
* a Wednesday of a leap year, then this
|
|
* year has only 52 weeks. */
|
|
int jan1 = (tm->tm_wday - tm->tm_yday + 371) % 7;
|
|
if (jan1 != 4 && (jan1 != 3 || !is_leap(tm->tm_year)))
|
|
val = 1;
|
|
}
|
|
return val;
|
|
}
|
|
|
|
const char *__tm_to_tzname(const struct tm *);
|
|
size_t __strftime_l(char *restrict, size_t, const char *restrict, const struct tm *restrict, locale_t);
|
|
|
|
const char *__strftime_fmt_1(char (*s)[100], size_t *l, int f, const struct tm *tm, locale_t loc)
|
|
{
|
|
nl_item item;
|
|
long long val;
|
|
const char *fmt;
|
|
int width = 2;
|
|
|
|
switch (f) {
|
|
case 'a':
|
|
item = ABDAY_1 + tm->tm_wday;
|
|
goto nl_strcat;
|
|
case 'A':
|
|
item = DAY_1 + tm->tm_wday;
|
|
goto nl_strcat;
|
|
case 'h':
|
|
case 'b':
|
|
item = ABMON_1 + tm->tm_mon;
|
|
goto nl_strcat;
|
|
case 'B':
|
|
item = MON_1 + tm->tm_mon;
|
|
goto nl_strcat;
|
|
case 'c':
|
|
item = D_T_FMT;
|
|
goto nl_strftime;
|
|
case 'C':
|
|
val = (1900LL+tm->tm_year) / 100;
|
|
goto number;
|
|
case 'd':
|
|
val = tm->tm_mday;
|
|
goto number;
|
|
case 'D':
|
|
fmt = "%m/%d/%y";
|
|
goto recu_strftime;
|
|
case 'e':
|
|
*l = snprintf(*s, sizeof *s, "%2d", tm->tm_mday);
|
|
return *s;
|
|
case 'F':
|
|
fmt = "%Y-%m-%d";
|
|
goto recu_strftime;
|
|
case 'g':
|
|
case 'G':
|
|
val = tm->tm_year + 1900LL;
|
|
if (tm->tm_yday < 3 && week_num(tm) != 1) val--;
|
|
else if (tm->tm_yday > 360 && week_num(tm) == 1) val++;
|
|
if (f=='g') val %= 100;
|
|
else width = 4;
|
|
goto number;
|
|
case 'H':
|
|
val = tm->tm_hour;
|
|
goto number;
|
|
case 'I':
|
|
val = tm->tm_hour;
|
|
if (!val) val = 12;
|
|
else if (val > 12) val -= 12;
|
|
goto number;
|
|
case 'j':
|
|
val = tm->tm_yday+1;
|
|
width = 3;
|
|
goto number;
|
|
case 'm':
|
|
val = tm->tm_mon+1;
|
|
goto number;
|
|
case 'M':
|
|
val = tm->tm_min;
|
|
goto number;
|
|
case 'n':
|
|
*l = 1;
|
|
return "\n";
|
|
case 'p':
|
|
item = tm->tm_hour >= 12 ? PM_STR : AM_STR;
|
|
goto nl_strcat;
|
|
case 'r':
|
|
item = T_FMT_AMPM;
|
|
goto nl_strftime;
|
|
case 'R':
|
|
fmt = "%H:%M";
|
|
goto recu_strftime;
|
|
case 's':
|
|
val = __tm_to_secs(tm) + tm->__tm_gmtoff;
|
|
goto number;
|
|
case 'S':
|
|
val = tm->tm_sec;
|
|
goto number;
|
|
case 't':
|
|
*l = 1;
|
|
return "\t";
|
|
case 'T':
|
|
fmt = "%H:%M:%S";
|
|
goto recu_strftime;
|
|
case 'u':
|
|
val = tm->tm_wday ? tm->tm_wday : 7;
|
|
width = 1;
|
|
goto number;
|
|
case 'U':
|
|
val = (tm->tm_yday + 7 - tm->tm_wday) / 7;
|
|
goto number;
|
|
case 'W':
|
|
val = (tm->tm_yday + 7 - (tm->tm_wday+6)%7) / 7;
|
|
goto number;
|
|
case 'V':
|
|
val = week_num(tm);
|
|
goto number;
|
|
case 'w':
|
|
val = tm->tm_wday;
|
|
width = 1;
|
|
goto number;
|
|
case 'x':
|
|
item = D_FMT;
|
|
goto nl_strftime;
|
|
case 'X':
|
|
item = T_FMT;
|
|
goto nl_strftime;
|
|
case 'y':
|
|
val = tm->tm_year % 100;
|
|
goto number;
|
|
case 'Y':
|
|
val = tm->tm_year + 1900;
|
|
if (val >= 10000) {
|
|
*l = snprintf(*s, sizeof *s, "+%lld", val);
|
|
return *s;
|
|
}
|
|
width = 4;
|
|
goto number;
|
|
case 'z':
|
|
if (tm->tm_isdst < 0) {
|
|
*l = 0;
|
|
return "";
|
|
}
|
|
*l = snprintf(*s, sizeof *s, "%+.2d%.2d",
|
|
(-tm->__tm_gmtoff)/3600,
|
|
abs(tm->__tm_gmtoff%3600)/60);
|
|
return *s;
|
|
case 'Z':
|
|
if (tm->tm_isdst < 0) {
|
|
*l = 0;
|
|
return "";
|
|
}
|
|
fmt = __tm_to_tzname(tm);
|
|
goto string;
|
|
case '%':
|
|
*l = 1;
|
|
return "%";
|
|
default:
|
|
return 0;
|
|
}
|
|
number:
|
|
*l = snprintf(*s, sizeof *s, "%0*lld", width, val);
|
|
return *s;
|
|
nl_strcat:
|
|
fmt = __nl_langinfo_l(item, loc);
|
|
string:
|
|
*l = strlen(fmt);
|
|
return fmt;
|
|
nl_strftime:
|
|
fmt = __nl_langinfo_l(item, loc);
|
|
recu_strftime:
|
|
*l = __strftime_l(*s, sizeof *s, fmt, tm, loc);
|
|
if (!*l) return 0;
|
|
return *s;
|
|
}
|
|
|
|
size_t __strftime_l(char *restrict s, size_t n, const char *restrict f, const struct tm *restrict tm, locale_t loc)
|
|
{
|
|
size_t l, k;
|
|
char buf[100];
|
|
char *p;
|
|
const char *t;
|
|
int plus;
|
|
unsigned long width;
|
|
for (l=0; l<n; f++) {
|
|
if (!*f) {
|
|
s[l] = 0;
|
|
return l;
|
|
}
|
|
if (*f != '%') {
|
|
s[l++] = *f;
|
|
continue;
|
|
}
|
|
f++;
|
|
if ((plus = (*f == '+'))) f++;
|
|
width = strtoul(f, &p, 10);
|
|
if (*p == 'C' || *p == 'F' || *p == 'G' || *p == 'Y') {
|
|
if (!width && p!=f) width = 1;
|
|
} else {
|
|
width = 0;
|
|
}
|
|
f = p;
|
|
if (*f == 'E' || *f == 'O') f++;
|
|
t = __strftime_fmt_1(&buf, &k, *f, tm, loc);
|
|
if (!t) break;
|
|
if (width) {
|
|
for (; *t=='+' || *t=='-' || (*t=='0'&&t[1]); t++, k--);
|
|
width--;
|
|
if (plus && tm->tm_year >= 10000-1900)
|
|
s[l++] = '+';
|
|
else if (tm->tm_year < -1900)
|
|
s[l++] = '-';
|
|
else
|
|
width++;
|
|
for (; width > k && l < n; width--)
|
|
s[l++] = '0';
|
|
}
|
|
if (k > n-l) k = n-l;
|
|
memcpy(s+l, t, k);
|
|
l += k;
|
|
}
|
|
if (n) {
|
|
if (l==n) l=n-1;
|
|
s[l] = 0;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
size_t strftime(char *restrict s, size_t n, const char *restrict f, const struct tm *restrict tm)
|
|
{
|
|
return __strftime_l(s, n, f, tm, 0);
|
|
}
|
|
|
|
weak_alias(__strftime_l, strftime_l);
|