Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

posix: implement _POSIX_THREAD_SAFE_FUNCTIONS's time functions #74180

Merged
merged 2 commits into from
Aug 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions doc/services/portability/posix/option_groups/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand All @@ -993,7 +993,7 @@ Enable this option with :kconfig:option:`CONFIG_POSIX_THREAD_SAFE_FUNCTIONS`.
getpwnam_r(),yes :ref:`†<posix_undefined_behaviour>`
getpwuid_r(),yes :ref:`†<posix_undefined_behaviour>`
gmtime_r(), yes
localtime_r(),
localtime_r(), yes (UTC timezone only)
putc_unlocked(),
putchar_unlocked(),
rand_r(), yes
Expand Down
3 changes: 3 additions & 0 deletions lib/libc/common/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
33 changes: 33 additions & 0 deletions lib/libc/common/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this one can be abbreviated without the UTC.

From here

Unlike localtime(), the localtime_r() function is not required to set tzname. If localtime_r() sets tzname, it shall also set daylight and timezone. If localtime_r() does not set tzname, it shall not set daylight and shall not set timezone. If the tm structure member tm_zone is accessed after the value of TZ is subsequently modified, the behaviour is undefined.

So if our implementation of localtime_r() never sets the timezone (i.e. is UTC-only), it's still compliant. No special treatment necessary. My assumption is that most would probably like to avoid bringing timezone details into Zephyr.

Suggested change
config COMMON_LIBC_LOCALTIME_R_UTC
config COMMON_LIBC_LOCALTIME_R

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

COMMON_LIBC_LOCALTIME_R_UTC is shared by both the localtime() & localtime_r(), I guess that's why I added that suffix to make it obvious that both functions only work for UTC, should I still remove the _UTC suffix? Or maybe I should create another Kconfig for localtime()?

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
Expand Down
56 changes: 56 additions & 0 deletions lib/libc/common/source/time/asctime.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* Copyright (c) 2024 Meta Platforms
*
* SPDX-License-Identifier: Apache-2.0
*/

#include <stdio.h>
#include <string.h>
#include <time.h>

#include <zephyr/sys/util.h>

#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 */
26 changes: 26 additions & 0 deletions lib/libc/common/source/time/ctime.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright (c) 2024 Meta Platforms
*
* SPDX-License-Identifier: Apache-2.0
*/

#include <time.h>

/**
* `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 */
17 changes: 17 additions & 0 deletions lib/libc/common/source/time/localtime_r_utc.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/*
* Copyright (c) 2024 Meta Platforms
*
* SPDX-License-Identifier: Apache-2.0
*/

#include <time.h>

struct tm *localtime_r(const time_t *timer, struct tm *result)
{
return gmtime_r(timer, result);
}
Comment on lines +9 to +12
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
struct tm *localtime_r(const time_t *timer, struct tm *result)
{
return gmtime_r(timer, result);
}
#ifdef CONFIG_LIBC_TIME_LOCALTIME_R
struct tm *localtime_r(const time_t *timer, struct tm *result)
{
return gmtime_r(timer, result);
}
#endif

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

localtime_r() isn't a C function?


struct tm *localtime(const time_t *timer)
{
return gmtime(timer);
}
3 changes: 3 additions & 0 deletions lib/libc/minimal/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
select COMMON_LIBC_LOCALTIME_R_UTC
select COMMON_LIBC_LOCALTIME_R

select COMMON_LIBC_CTIME
default y
help
Enable time() and gmtime_r() for the minimal libc.
Expand Down
12 changes: 11 additions & 1 deletion lib/libc/minimal/include/time.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
cfriedt marked this conversation as resolved.
Show resolved Hide resolved
struct tm *localtime_r(const time_t *ZRESTRICT timer, struct tm *ZRESTRICT result);

time_t time(time_t *tloc);

Expand Down
1 change: 1 addition & 0 deletions lib/posix/options/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
18 changes: 18 additions & 0 deletions lib/posix/options/Kconfig.c_lang_r
Original file line number Diff line number Diff line change
@@ -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
9 changes: 9 additions & 0 deletions lib/posix/options/Kconfig.fs
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,13 @@ config POSIX_FILE_SYSTEM_ALIAS_FSTAT
help
Select 'y' here and Zephyr will provide an alias for fstat() as _fstat().

config POSIX_FILE_SYSTEM_R
bool "Thread-Safe File System"
help
Select 'y' here and Zephyr will provide an implementation of the POSIX_FILE_SYSTEM_R
Option Group, consisting of readdir_r().

For more informnation, please see
https://pubs.opengroup.org/onlinepubs/9699919799/xrat/V4_subprofiles.html

endif # POSIX_FILE_SYSTEM
2 changes: 2 additions & 0 deletions lib/posix/options/Kconfig.pthread
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,8 @@ 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(),
Expand Down
4 changes: 2 additions & 2 deletions lib/posix/options/fs.c
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,7 @@ struct dirent *readdir(DIR *dirp)
return &pdirent;
}

#ifdef CONFIG_POSIX_THREAD_SAFE_FUNCTIONS
#ifdef CONFIG_POSIX_FILE_SYSTEM_R
int readdir_r(DIR *dirp, struct dirent *entry, struct dirent **result)
{
struct dirent *dir;
Expand Down Expand Up @@ -346,7 +346,7 @@ int readdir_r(DIR *dirp, struct dirent *entry, struct dirent **result)

return 0;
}
#endif /* CONFIG_POSIX_THREAD_SAFE_FUNCTIONS */
#endif /* CONFIG_POSIX_FILE_SYSTEM_R */

/**
* @brief Rename a file.
Expand Down
83 changes: 82 additions & 1 deletion tests/lib/c_lib/common/src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

#include <zephyr/kernel.h>
#include <zephyr/sys/__assert.h>
#include <zephyr/sys/util.h>
#include <zephyr/ztest.h>

#include <limits.h>
Expand Down Expand Up @@ -1075,7 +1076,7 @@
*
* @see gmtime(),gmtime_r().
*/
ZTEST(libc_common, test_time)
ZTEST(libc_common, test_time_gmtime)
{
time_t tests1 = 0;
time_t tests2 = -5;
Expand All @@ -1092,6 +1093,86 @@
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;

Check notice on line 1147 in tests/lib/c_lib/common/src/main.c

View workflow job for this annotation

GitHub Actions / Run compliance checks on patch series (PR)

You may want to run clang-format on this change

tests/lib/c_lib/common/src/main.c:1147 - time_t tests3 = (time_t) -214748364800; + time_t tests3 = (time_t)-214748364800;

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
Expand Down
2 changes: 2 additions & 0 deletions tests/lib/c_lib/common/testcase.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading