diff --git a/doc/services/portability/posix/option_groups/index.rst b/doc/services/portability/posix/option_groups/index.rst index 5526b7b1a1a..ad809cd44d9 100644 --- a/doc/services/portability/posix/option_groups/index.rst +++ b/doc/services/portability/posix/option_groups/index.rst @@ -981,8 +981,8 @@ Enable this option with :kconfig:option:`CONFIG_POSIX_THREAD_SAFE_FUNCTIONS`. :header: API, Supported :widths: 50,10 - asctime_r(), - ctime_r(), + asctime_r(), yes + ctime_r(), yes (UTC timezone only) flockfile(), ftrylockfile(), funlockfile(), @@ -993,7 +993,7 @@ Enable this option with :kconfig:option:`CONFIG_POSIX_THREAD_SAFE_FUNCTIONS`. getpwnam_r(),yes :ref:`†` getpwuid_r(),yes :ref:`†` gmtime_r(), yes - localtime_r(), + localtime_r(), yes (UTC timezone only) putc_unlocked(), putchar_unlocked(), rand_r(), yes diff --git a/lib/libc/common/CMakeLists.txt b/lib/libc/common/CMakeLists.txt index 64fe33d1c4a..779e5735a8b 100644 --- a/lib/libc/common/CMakeLists.txt +++ b/lib/libc/common/CMakeLists.txt @@ -5,7 +5,10 @@ zephyr_system_include_directories(include) zephyr_library() zephyr_library_property(ALLOW_EMPTY TRUE) zephyr_library_sources_ifdef(CONFIG_COMMON_LIBC_ABORT source/stdlib/abort.c) +zephyr_library_sources_ifdef(CONFIG_COMMON_LIBC_ASCTIME source/time/asctime.c) zephyr_library_sources_ifdef(CONFIG_COMMON_LIBC_GMTIME_R source/time/gmtime_r.c) +zephyr_library_sources_ifdef(CONFIG_COMMON_LIBC_LOCALTIME_R_UTC source/time/localtime_r_utc.c) +zephyr_library_sources_ifdef(CONFIG_COMMON_LIBC_CTIME source/time/ctime.c) zephyr_library_sources_ifdef(CONFIG_COMMON_LIBC_TIME source/time/time.c) zephyr_library_sources_ifdef(CONFIG_COMMON_LIBC_MALLOC source/stdlib/malloc.c) zephyr_library_sources_ifdef(CONFIG_COMMON_LIBC_STRNLEN source/string/strnlen.c) diff --git a/lib/libc/common/Kconfig b/lib/libc/common/Kconfig index ea0d6869435..8d510af57c0 100644 --- a/lib/libc/common/Kconfig +++ b/lib/libc/common/Kconfig @@ -6,11 +6,44 @@ config COMMON_LIBC_ABORT help common implementation of abort(). +config COMMON_LIBC_ASCTIME + bool + help + common implementation of asctime(). + +config COMMON_LIBC_ASCTIME_R + bool "Thread-safe version of asctime()" + default y if POSIX_THREAD_SAFE_FUNCTIONS + select COMMON_LIBC_ASCTIME + help + common implementation of asctime_r(). + +config COMMON_LIBC_CTIME + bool + select COMMON_LIBC_LOCALTIME_R_UTC + help + common implementation of ctime(). + +config COMMON_LIBC_CTIME_R + bool "Thread-safe version of ctime()" + default y if POSIX_THREAD_SAFE_FUNCTIONS + select COMMON_LIBC_CTIME + help + common implementation of ctime_r(). + config COMMON_LIBC_GMTIME_R bool help common implementation of gmtime_r(). +config COMMON_LIBC_LOCALTIME_R_UTC + bool + select COMMON_LIBC_GMTIME_R + help + Simple implementation of localtime() & localtime_r(). + This option just wraps around the gmtime(), the result is always expressed as + Coordinated Universal Time (UTC). + config COMMON_LIBC_TIME bool help diff --git a/lib/libc/common/source/time/asctime.c b/lib/libc/common/source/time/asctime.c new file mode 100644 index 00000000000..aea84cfe0e0 --- /dev/null +++ b/lib/libc/common/source/time/asctime.c @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2024 Meta Platforms + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#include + +#define DATE_STRING_BUF_SZ 26U +#define DATE_WDAY_STRING_SZ 7U +#define DATE_MON_STRING_SZ 12U +#define DATE_TM_YEAR_BASE 1900 + +static char *asctime_impl(const struct tm *tp, char *buf) +{ + static const char wday_str[DATE_WDAY_STRING_SZ][3] = { + "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", + }; + static const char mon_str[DATE_MON_STRING_SZ][3] = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", + }; + + if ((buf == NULL) || (tp == NULL) || ((unsigned int)tp->tm_wday >= DATE_WDAY_STRING_SZ) || + ((unsigned int)tp->tm_mon >= DATE_MON_STRING_SZ)) { + return NULL; + } + + unsigned int n = (unsigned int)snprintf( + buf, DATE_STRING_BUF_SZ, "%.3s %.3s%3d %.2d:%.2d:%.2d %d\n", wday_str[tp->tm_wday], + mon_str[tp->tm_mon], tp->tm_mday, tp->tm_hour, tp->tm_min, tp->tm_sec, + DATE_TM_YEAR_BASE + tp->tm_year); + + if (n >= DATE_STRING_BUF_SZ) { + return NULL; + } + + return buf; +} + +char *asctime(const struct tm *tp) +{ + static char buf[DATE_STRING_BUF_SZ]; + + return asctime_impl(tp, buf); +} + +#if defined(CONFIG_COMMON_LIBC_ASCTIME_R) +char *asctime_r(const struct tm *tp, char *buf) +{ + return asctime_impl(tp, buf); +} +#endif /* CONFIG_COMMON_LIBC_ASCTIME_R */ diff --git a/lib/libc/common/source/time/ctime.c b/lib/libc/common/source/time/ctime.c new file mode 100644 index 00000000000..454feb363c5 --- /dev/null +++ b/lib/libc/common/source/time/ctime.c @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2024 Meta Platforms + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +/** + * `ctime()` is equivalent to `asctime(localtime(clock))` + * See: https://pubs.opengroup.org/onlinepubs/009695399/functions/ctime.html + */ + +char *ctime(const time_t *clock) +{ + return asctime(localtime(clock)); +} + +#if defined(CONFIG_COMMON_LIBC_CTIME_R) +char *ctime_r(const time_t *clock, char *buf) +{ + struct tm tmp; + + return asctime_r(localtime_r(clock, &tmp), buf); +} +#endif /* CONFIG_COMMON_LIBC_CTIME_R */ diff --git a/lib/libc/common/source/time/localtime_r_utc.c b/lib/libc/common/source/time/localtime_r_utc.c new file mode 100644 index 00000000000..7a17dcf442c --- /dev/null +++ b/lib/libc/common/source/time/localtime_r_utc.c @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2024 Meta Platforms + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +struct tm *localtime_r(const time_t *timer, struct tm *result) +{ + return gmtime_r(timer, result); +} + +struct tm *localtime(const time_t *timer) +{ + return gmtime(timer); +} diff --git a/lib/libc/minimal/Kconfig b/lib/libc/minimal/Kconfig index 40aa95aa6a1..0b99994963d 100644 --- a/lib/libc/minimal/Kconfig +++ b/lib/libc/minimal/Kconfig @@ -39,6 +39,9 @@ config MINIMAL_LIBC_TIME bool "Time functions" select COMMON_LIBC_TIME if POSIX_TIMERS select COMMON_LIBC_GMTIME_R + select COMMON_LIBC_ASCTIME + select COMMON_LIBC_LOCALTIME_R_UTC + select COMMON_LIBC_CTIME default y help Enable time() and gmtime_r() for the minimal libc. diff --git a/lib/libc/minimal/include/time.h b/lib/libc/minimal/include/time.h index e0dae58ff32..62ec45b03a1 100644 --- a/lib/libc/minimal/include/time.h +++ b/lib/libc/minimal/include/time.h @@ -45,12 +45,22 @@ typedef _SUSECONDS_T_ suseconds_t; /* * Conversion between civil time and UNIX time. The companion - * localtime() and inverse mktime() are not provided here since they + * mktime() is not provided here since it * require access to time zone information. + * + * The localtime() & localtime_r() simply + * wraps around the gmtime() & gmtime_r() functions, the + * results are always expressed as UTC. */ struct tm *gmtime(const time_t *timep); struct tm *gmtime_r(const time_t *ZRESTRICT timep, struct tm *ZRESTRICT result); +char *asctime(const struct tm *timeptr); +struct tm *localtime(const time_t *timer); +char *ctime(const time_t *clock); +char *asctime_r(const struct tm *ZRESTRICT tp, char *ZRESTRICT buf); +char *ctime_r(const time_t *clock, char *buf); +struct tm *localtime_r(const time_t *ZRESTRICT timer, struct tm *ZRESTRICT result); time_t time(time_t *tloc); diff --git a/lib/posix/options/Kconfig b/lib/posix/options/Kconfig index f3b6bda1282..f453f5295d0 100644 --- a/lib/posix/options/Kconfig +++ b/lib/posix/options/Kconfig @@ -10,6 +10,7 @@ rsource "Kconfig.profile" rsource "Kconfig.aio" rsource "Kconfig.barrier" +rsource "Kconfig.c_lang_r" rsource "Kconfig.c_lib_ext" rsource "Kconfig.device_io" rsource "Kconfig.fd_mgmt" diff --git a/lib/posix/options/Kconfig.c_lang_r b/lib/posix/options/Kconfig.c_lang_r new file mode 100644 index 00000000000..d8d0f4a2751 --- /dev/null +++ b/lib/posix/options/Kconfig.c_lang_r @@ -0,0 +1,18 @@ +# Copyright (c) 2024 Tenstorrent AI ULC +# Copyright (c) 2024 Meta Platforms +# +# SPDX-License-Identifier: Apache-2.0 + +config POSIX_C_LANG_SUPPORT_R + bool "Thread-Safe General ISO C Library" + select COMMON_LIBC_ASCTIME_R + select COMMON_LIBC_CTIME_R + select COMMON_LIBC_GMTIME_R + select COMMON_LIBC_LOCALTIME_R_UTC + help + Select 'y' here and Zephyr will provide an implementation of the POSIX_C_LANG_SUPPORT_R + Option Group, consisting of asctime_r(), ctime_r(), gmtime_r(), localtime_r(), rand_r(), + strerror_r(), and strtok_r() + + For more informnation, please see + https://pubs.opengroup.org/onlinepubs/9699919799/xrat/V4_subprofiles.html diff --git a/lib/posix/options/Kconfig.pthread b/lib/posix/options/Kconfig.pthread index eb0800e5e1f..f821d9defeb 100644 --- a/lib/posix/options/Kconfig.pthread +++ b/lib/posix/options/Kconfig.pthread @@ -157,6 +157,7 @@ config POSIX_THREAD_PRIO_PROTECT config POSIX_THREAD_SAFE_FUNCTIONS bool "POSIX thread-safe functions" select POSIX_FILE_SYSTEM_R if POSIX_FILE_SYSTEM + select POSIX_C_LANG_SUPPORT_R help Select 'y' here to enable POSIX thread-safe functions including asctime_r(), ctime_r(), flockfile(), ftrylockfile(), funlockfile(), getc_unlocked(), getchar_unlocked(), diff --git a/tests/lib/c_lib/common/src/main.c b/tests/lib/c_lib/common/src/main.c index 4380b2cde8f..f7db63c0ad8 100644 --- a/tests/lib/c_lib/common/src/main.c +++ b/tests/lib/c_lib/common/src/main.c @@ -22,6 +22,7 @@ #include #include +#include #include #include @@ -1075,7 +1076,7 @@ ZTEST(libc_common, test_strtok_r) * * @see gmtime(),gmtime_r(). */ -ZTEST(libc_common, test_time) +ZTEST(libc_common, test_time_gmtime) { time_t tests1 = 0; time_t tests2 = -5; @@ -1092,6 +1093,86 @@ ZTEST(libc_common, test_time) zassert_not_null(gmtime_r(&tests4, &tp), "gmtime_r failed"); } +/** + * @brief Test time function + * + * @see asctime(), asctime_r(). + */ +ZTEST(libc_common, test_time_asctime) +{ + char buf[26] = {0}; + struct tm tp = { + .tm_sec = 10, /* Seconds */ + .tm_min = 30, /* Minutes */ + .tm_hour = 14, /* Hour (24-hour format) */ + .tm_wday = 5, /* Day of the week (0-6, 0 = Sun) */ + .tm_mday = 1, /* Day of the month */ + .tm_mon = 5, /* Month (0-11, January = 0) */ + .tm_year = 124, /* Year (current year - 1900) */ + }; + + zassert_not_null(asctime_r(&tp, buf)); + zassert_equal(strncmp("Fri Jun 1 14:30:10 2024\n", buf, sizeof(buf)), 0); + + zassert_not_null(asctime(&tp)); + zassert_equal(strncmp("Fri Jun 1 14:30:10 2024\n", asctime(&tp), sizeof(buf)), 0); + + if (IS_ENABLED(CONFIG_COMMON_LIBC_ASCTIME_R)) { + zassert_is_null(asctime_r(NULL, buf)); + zassert_is_null(asctime(NULL)); + + zassert_is_null(asctime_r(&tp, NULL)); + + tp.tm_wday = 8; + zassert_is_null(asctime_r(&tp, buf)); + zassert_is_null(asctime(&tp)); + + tp.tm_wday = 5; + tp.tm_mon = 12; + zassert_is_null(asctime_r(&tp, buf)); + zassert_is_null(asctime(&tp)); + } +} + +/** + * @brief Test time function + * + * @see localtime(), localtime_r(). + */ +ZTEST(libc_common, test_time_localtime) +{ + time_t tests1 = 0; + time_t tests2 = -5; + time_t tests3 = (time_t) -214748364800; + time_t tests4 = 951868800; + + struct tm tp; + + zassert_not_null(localtime(&tests1), "localtime failed"); + zassert_not_null(localtime(&tests2), "localtime failed"); + + tp.tm_wday = -5; + zassert_not_null(localtime_r(&tests3, &tp), "localtime_r failed"); + zassert_not_null(localtime_r(&tests4, &tp), "localtime_r failed"); +} + +/** + * @brief Test time function + * + * @see ctime(), ctime_r(). + */ +ZTEST(libc_common, test_time_ctime) +{ + char buf[26] = {0}; + time_t test1 = 1718260000; + + zassert_not_null(ctime_r(&test1, buf)); + zassert_equal(strncmp("Thu Jun 13 06:26:40 2024\n", buf, sizeof(buf)), 0); + + zassert_not_null(ctime(&test1)); + zassert_equal(strncmp("Thu Jun 13 06:26:40 2024\n", ctime(&test1), sizeof(buf)), 0); +} + /** * * @brief Test rand function diff --git a/tests/lib/c_lib/common/testcase.yaml b/tests/lib/c_lib/common/testcase.yaml index 4d184b9536f..40a33afe0cf 100644 --- a/tests/lib/c_lib/common/testcase.yaml +++ b/tests/lib/c_lib/common/testcase.yaml @@ -13,6 +13,8 @@ tests: - CONFIG_MINIMAL_LIBC=y - CONFIG_MINIMAL_LIBC_NON_REENTRANT_FUNCTIONS=y - CONFIG_MINIMAL_LIBC_RAND=y + - CONFIG_COMMON_LIBC_ASCTIME_R=y + - CONFIG_COMMON_LIBC_CTIME_R=y libraries.libc.common.newlib: filter: CONFIG_NEWLIB_LIBC_SUPPORTED min_ram: 32