From 58390201e429b0131e9c249782908a82c05baeb4 Mon Sep 17 00:00:00 2001 From: Chris Friedt Date: Sat, 28 Dec 2024 13:54:21 -0500 Subject: [PATCH 01/13] posix: fs: perform a no-op stat ioctl operation Newlib currently relies on calling stat() indirectly via _fstat_r() when performing certain stdio file operations (e.g. reading a file). However, that results in errno being set to 95 (EOPNOTSUPP) in unexpected places, which causes non-determinism and unpredictability, and it is inconsistent with both picolibc and the minimal libc. Normally, it would be a great opportunity to implement zvfs_stat() via fs_stat(). However, the current implementation of fs_stat() is not particularly useful to call from zvfs_stat() since fs_state() relies on an absolute path passed as the first parameter and it is currently _difficult_ to get that from an integer file descriptor. In the ideal world, it would be possible to extrapolate the absolute path of a filename from a fs_file_t oobject or some other filesystem subsystem API. Since it is not practical to modify Newlib to change the way it performs standard C I/O, simply add a no-op operations for the fs subsystem vtable. Signed-off-by: Chris Friedt --- lib/posix/options/fs.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/lib/posix/options/fs.c b/lib/posix/options/fs.c index 45028248608f..0c8da596cc67 100644 --- a/lib/posix/options/fs.c +++ b/lib/posix/options/fs.c @@ -183,6 +183,19 @@ static int fs_ioctl_vmeth(void *obj, unsigned int request, va_list args) } break; } + case ZFD_IOCTL_STAT: { + struct stat *buf; + + buf = va_arg(args, struct stat *); + + /* + * fs_stat() is kind of broken. The problem is that newlib calls stat() prior to + * doing certain stdio file operations. Simply clear the buffer to prevent errno + * from being set to EOPNOTSUPP. + */ + memset(buf, 0, sizeof(*buf)); + return 0; + } default: errno = EOPNOTSUPP; return -1; From c35c9ecc7342741d316414b97e7920d4af90997f Mon Sep 17 00:00:00 2001 From: Chris Friedt Date: Tue, 24 Dec 2024 12:44:46 -0500 Subject: [PATCH 02/13] os: fdtable: provide z_libc_file_alloc() abstraction Previously, there was an implicit assumption that Zephyr's internal struct fd_entry * was synonymous with FILE * from the C library. This is generally not the case and aliasing these two distinct types was preventing a fair bit of functionality from Just Working - namely stdio function calls like fgets() and fopen(). The problem can be seen directly when trying to use a function like zvfs_fdopen(). Instead of aliasing the two types, require that all Zephyr C libraries provide a z_libc_file_alloc() function that allocates and populates the required fields of a FILE object. Zephyr currently only provides the integer file descriptor to the C library for initializing FILE objects. Signed-off-by: Chris Friedt --- lib/libc/newlib/CMakeLists.txt | 5 +- lib/libc/newlib/z_libc.c | 82 ++++++++++++++++++++++++++++++++ lib/libc/picolibc/CMakeLists.txt | 1 + lib/libc/picolibc/z_libc.c | 60 +++++++++++++++++++++++ lib/os/fdtable.c | 35 ++++++++++++-- 5 files changed, 178 insertions(+), 5 deletions(-) create mode 100644 lib/libc/newlib/z_libc.c create mode 100644 lib/libc/picolibc/z_libc.c diff --git a/lib/libc/newlib/CMakeLists.txt b/lib/libc/newlib/CMakeLists.txt index d3dc448eccaf..0e9e7fbcb0e4 100644 --- a/lib/libc/newlib/CMakeLists.txt +++ b/lib/libc/newlib/CMakeLists.txt @@ -1,7 +1,10 @@ # SPDX-License-Identifier: Apache-2.0 zephyr_library() -zephyr_library_sources(libc-hooks.c) +zephyr_library_sources( + libc-hooks.c + z_libc.c +) # Do not allow LTO when compiling libc-hooks.c file set_source_files_properties(libc-hooks.c PROPERTIES COMPILE_OPTIONS $) diff --git a/lib/libc/newlib/z_libc.c b/lib/libc/newlib/z_libc.c new file mode 100644 index 000000000000..dd114449b97e --- /dev/null +++ b/lib/libc/newlib/z_libc.c @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2024 Tenstorrent AI ULC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#include + +extern void *_read_r; +extern void *_write_r; +extern void *_lseek_r; +extern void *_close_r; + +static int z_libc_sflags(const char *mode) +{ + int ret = 0; + + switch (mode[0]) { + case 'r': + ret = ZVFS_O_RDONLY; + break; + + case 'w': + ret = ZVFS_O_WRONLY | ZVFS_O_CREAT | ZVFS_O_TRUNC; + break; + + case 'a': + ret = ZVFS_O_WRONLY | ZVFS_O_CREAT | ZVFS_O_APPEND; + break; + default: + return 0; + } + + while (*++mode) { + switch (*mode) { + case '+': + ret |= ZVFS_O_RDWR; + break; + case 'x': + ret |= ZVFS_O_EXCL; + break; + default: + break; + } + } + + return ret; +} + +FILE *z_libc_file_alloc(int fd, const char *mode) +{ + FILE *fp; + + fp = calloc(1, sizeof(*fp)); + if (fp == NULL) { + return NULL; + } + + fp->_flags = z_libc_sflags(mode); + fp->_file = fd; + fp->_cookie = (void *)fp; + + *((void **)fp->_read) = _read_r; + *((void **)fp->_write) = _write_r; + *((void **)fp->_seek) = _lseek_r; + *((void **)fp->_close) = _close_r; + + return fp; +} + +int z_libc_file_get_fd(FILE *fp) +{ + if (fp == NULL) { + return -EINVAL; + } + + return fp->_file; +} diff --git a/lib/libc/picolibc/CMakeLists.txt b/lib/libc/picolibc/CMakeLists.txt index 752379dd76d4..ff83af9b8dbd 100644 --- a/lib/libc/picolibc/CMakeLists.txt +++ b/lib/libc/picolibc/CMakeLists.txt @@ -9,6 +9,7 @@ zephyr_library_sources( exit.c locks.c stdio.c + z_libc.c ) zephyr_library_compile_options($) diff --git a/lib/libc/picolibc/z_libc.c b/lib/libc/picolibc/z_libc.c new file mode 100644 index 000000000000..79069238a307 --- /dev/null +++ b/lib/libc/picolibc/z_libc.c @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2024 Tenstorrent AI ULC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "stdio-bufio.h" + +#include +#include +#include + +#include + +#define FDEV_SETUP_ZVFS(fd, buf, size, rwflags, bflags) \ + FDEV_SETUP_BUFIO(fd, buf, size, zvfs_read_wrap, zvfs_write_wrap, zvfs_lseek, zvfs_close, \ + rwflags, bflags) + +int __posix_sflags(const char *mode, int *optr); + +/* FIXME: do not use ssize_t or off_t */ +ssize_t zvfs_read(int fd, void *buf, size_t sz, const size_t *from_offset); +ssize_t zvfs_write(int fd, const void *buf, size_t sz, const size_t *from_offset); +off_t zvfs_lseek(int fd, off_t offset, int whence); +int zvfs_close(int fd); + +static ssize_t zvfs_read_wrap(int fd, void *buf, size_t sz) +{ + return zvfs_read(fd, buf, sz, NULL); +} + +static ssize_t zvfs_write_wrap(int fd, const void *buf, size_t sz) +{ + return zvfs_write(fd, buf, sz, NULL); +} + +FILE *z_libc_file_alloc(int fd, const char *mode) +{ + struct __file_bufio *bf; + int __maybe_unused unused; + + bf = calloc(1, sizeof(struct __file_bufio) + BUFSIZ); + if (bf == NULL) { + return NULL; + } + + *bf = (struct __file_bufio)FDEV_SETUP_ZVFS(fd, (char *)(bf + 1), BUFSIZ, + __posix_sflags(mode, &unused), __BFALL); + + __bufio_lock_init(&(bf->xfile.cfile.file)); + + return &(bf->xfile.cfile.file); +} + +int z_libc_file_get_fd(const FILE *fp) +{ + const struct __file_bufio *const bf = (const struct __file_bufio *)fp; + + return POINTER_TO_INT(bf->ptr); +} diff --git a/lib/os/fdtable.c b/lib/os/fdtable.c index be53627efca7..013fb4b07cb9 100644 --- a/lib/os/fdtable.c +++ b/lib/os/fdtable.c @@ -24,6 +24,11 @@ #include #include +#ifndef CONFIG_MINIMAL_LIBC +extern FILE *z_libc_file_alloc(int fd, const char *mode); +extern int z_libc_file_get_fd(const FILE *fp); +#endif + struct stat; struct fd_entry { @@ -75,6 +80,20 @@ static struct fd_entry fdtable[CONFIG_ZVFS_OPEN_MAX] = { static K_MUTEX_DEFINE(fdtable_lock); +#ifdef CONFIG_MINIMAL_LIBC +static ALWAYS_INLINE inline FILE *z_libc_file_alloc(int fd, const char *mode) +{ + ARG_UNUSED(mode); + + return (FILE *)&fdtable[fd]; +} + +static ALWAYS_INLINE inline int z_libc_file_get_fd(const FILE *fp) +{ + return (const struct fd_entry *)fp - fdtable; +} +#endif + static int z_fd_ref(int fd) { return atomic_inc(&fdtable[fd].refcount) + 1; @@ -313,6 +332,7 @@ static bool supports_pread_pwrite(uint32_t mode) { switch (mode & ZVFS_MODE_IFMT) { case ZVFS_MODE_IFSHM: + case ZVFS_MODE_IFREG: return true; default: return false; @@ -413,23 +433,30 @@ int zvfs_close(int fd) FILE *zvfs_fdopen(int fd, const char *mode) { - ARG_UNUSED(mode); + FILE *ret; if (_check_fd(fd) < 0) { return NULL; } - return (FILE *)&fdtable[fd]; + ret = z_libc_file_alloc(fd, mode); + if (ret == NULL) { + errno = ENOMEM; + } + + return ret; } int zvfs_fileno(FILE *file) { - if (!IS_ARRAY_ELEMENT(fdtable, file)) { + int fd = z_libc_file_get_fd(file); + + if (fd < 0 || fd >= ARRAY_SIZE(fdtable)) { errno = EBADF; return -1; } - return (struct fd_entry *)file - fdtable; + return fd; } int zvfs_fstat(int fd, struct stat *buf) From b685d3bf651a4cd207b509ba3e13e98c1edf9e51 Mon Sep 17 00:00:00 2001 From: Chris Friedt Date: Tue, 24 Dec 2024 12:34:57 -0500 Subject: [PATCH 03/13] posix: options: implement POSIX_SYSTEM_DATABASE_R Provide an implementation for the POSIX_SYSTEM_DATABASE_R Option Group. This group is required for all conforming implementations and is therefore required for PSE51, PSE52, PSE53, PSE54, and all other possible subprofiles. Signed-off-by: Chris Friedt --- include/zephyr/posix/grp.h | 12 +- include/zephyr/posix/pwd.h | 12 +- include/zephyr/posix/sys/sysconf.h | 4 +- lib/posix/options/CMakeLists.txt | 9 +- lib/posix/options/Kconfig | 1 + lib/posix/options/Kconfig.system_database_r | 42 +++ lib/posix/options/grp.c | 37 --- lib/posix/options/pwd.c | 37 --- lib/posix/options/system_database_priv.c | 272 ++++++++++++++++++++ lib/posix/options/system_database_priv.h | 15 ++ lib/posix/options/system_database_r.c | 38 +++ 11 files changed, 399 insertions(+), 80 deletions(-) create mode 100644 lib/posix/options/Kconfig.system_database_r delete mode 100644 lib/posix/options/grp.c delete mode 100644 lib/posix/options/pwd.c create mode 100644 lib/posix/options/system_database_priv.c create mode 100644 lib/posix/options/system_database_priv.h create mode 100644 lib/posix/options/system_database_r.c diff --git a/include/zephyr/posix/grp.h b/include/zephyr/posix/grp.h index 3f5616c34e1b..15f3f4b8f34e 100644 --- a/include/zephyr/posix/grp.h +++ b/include/zephyr/posix/grp.h @@ -1,5 +1,6 @@ /* * Copyright (c) 2024 Meta Platforms + * Copyright (c) 2024 Tenstorrent AI ULC * * SPDX-License-Identifier: Apache-2.0 */ @@ -24,9 +25,18 @@ struct group { char **gr_mem; }; +#if defined(_XOPEN_SOURCE) +void endgrent(void); +struct group *getgrent(void); +#endif +struct group *getgrgid(gid_t gid); +int getgrgid_r(gid_t gid, struct group *grp, char *buffer, size_t bufsize, struct group **result); +struct group *getgrnam(const char *name); int getgrnam_r(const char *name, struct group *grp, char *buffer, size_t bufsize, struct group **result); -int getgrgid_r(gid_t gid, struct group *grp, char *buffer, size_t bufsize, struct group **result); +#if defined(_XOPEN_SOURCE) +void setgrent(void); +#endif #ifdef __cplusplus } diff --git a/include/zephyr/posix/pwd.h b/include/zephyr/posix/pwd.h index 3557b20ab979..76cf1918dcd1 100644 --- a/include/zephyr/posix/pwd.h +++ b/include/zephyr/posix/pwd.h @@ -1,5 +1,6 @@ /* * Copyright (c) 2024 Meta Platforms + * Copyright (c) 2024 Tenstorrent AI ULC * * SPDX-License-Identifier: Apache-2.0 */ @@ -25,9 +26,18 @@ struct passwd { char *pw_shell; }; -int getpwnam_r(const char *nam, struct passwd *pwd, char *buffer, size_t bufsize, +#if defined(_XOPEN_SOURCE) +void endpwent(void); +struct passwd *getpwent(void); +#endif +struct passwd *getpwnam(const char *name); +int getpwnam_r(const char *name, struct passwd *pwd, char *buffer, size_t bufsize, struct passwd **result); +struct passwd *getpwuid(uid_t uid); int getpwuid_r(uid_t uid, struct passwd *pwd, char *buffer, size_t bufsize, struct passwd **result); +#if defined(_XOPEN_SOURCE) +void setpwent(void); +#endif #ifdef __cplusplus } diff --git a/include/zephyr/posix/sys/sysconf.h b/include/zephyr/posix/sys/sysconf.h index 67bf84bfb755..cb8d0ae61ea5 100644 --- a/include/zephyr/posix/sys/sysconf.h +++ b/include/zephyr/posix/sys/sysconf.h @@ -264,8 +264,8 @@ enum { #define __z_posix_sysconf_SC_XOPEN_UUCP (-1L) #define __z_posix_sysconf_SC_XOPEN_VERSION _XOPEN_VERSION #define __z_posix_sysconf_SC_CLK_TCK (100L) -#define __z_posix_sysconf_SC_GETGR_R_SIZE_MAX (0L) -#define __z_posix_sysconf_SC_GETPW_R_SIZE_MAX (0L) +#define __z_posix_sysconf_SC_GETGR_R_SIZE_MAX CONFIG_POSIX_GETGR_R_SIZE_MAX +#define __z_posix_sysconf_SC_GETPW_R_SIZE_MAX CONFIG_POSIX_GETPW_R_SIZE_MAX #define __z_posix_sysconf_SC_AIO_LISTIO_MAX AIO_LISTIO_MAX #define __z_posix_sysconf_SC_AIO_MAX AIO_MAX #define __z_posix_sysconf_SC_AIO_PRIO_DELTA_MAX AIO_PRIO_DELTA_MAX diff --git a/lib/posix/options/CMakeLists.txt b/lib/posix/options/CMakeLists.txt index f12904aefb60..ebe6e2901079 100644 --- a/lib/posix/options/CMakeLists.txt +++ b/lib/posix/options/CMakeLists.txt @@ -121,6 +121,13 @@ if (NOT CONFIG_TC_PROVIDES_POSIX_SPIN_LOCKS) zephyr_library_sources_ifdef(CONFIG_POSIX_SPIN_LOCKS spinlock.c) endif() +if (NOT CONFIG_TC_PROVIDES_POSIX_SYSTEM_DATABASE_R) + zephyr_library_sources_ifdef(CONFIG_POSIX_SYSTEM_DATABASE_R + system_database_priv.c + system_database_r.c + ) +endif() + if (NOT CONFIG_TC_PROVIDES_POSIX_TIMERS) zephyr_library_sources_ifdef(CONFIG_POSIX_TIMERS clock.c @@ -145,11 +152,9 @@ if (NOT CONFIG_TC_PROVIDES_POSIX_THREADS) # We have opted to use POSIX_THREADS here to match the Option name. zephyr_library_sources_ifdef(CONFIG_POSIX_THREADS cond.c - grp.c key.c mutex.c pthread.c - pwd.c ) endif() diff --git a/lib/posix/options/Kconfig b/lib/posix/options/Kconfig index 6c2b129170e3..950fa98730d6 100644 --- a/lib/posix/options/Kconfig +++ b/lib/posix/options/Kconfig @@ -28,6 +28,7 @@ rsource "Kconfig.semaphore" rsource "Kconfig.signal" rsource "Kconfig.spinlock" rsource "Kconfig.sync_io" +rsource "Kconfig.system_database_r" rsource "Kconfig.timer" menu "X/Open system interfaces" diff --git a/lib/posix/options/Kconfig.system_database_r b/lib/posix/options/Kconfig.system_database_r new file mode 100644 index 000000000000..166141c45e97 --- /dev/null +++ b/lib/posix/options/Kconfig.system_database_r @@ -0,0 +1,42 @@ +# Copyright (c) 2024 Tenstorrent AI ULC +# +# SPDX-License-Identifier: Apache-2.0 + +config POSIX_SYSTEM_DATABASE_R + bool "POSIX System Database" + help + Select 'y' here, and the system will support getgrgid_r(), getgrnam_r(), getpwnam_r(), and + getpwuid_r(). + + For more information, please see + https://pubs.opengroup.org/onlinepubs/9699919799/xrat/V4_subprofiles.html + +config POSIX_GETGR_R_SIZE_MAX + int "Initial size of getgrgid_r() and getgrnam_r() data buffers" + default 96 + help + This option sets the maximum size of the buffer for getgrgid_r() and getgrnam_r(). + + The default size is derived from the formula below. Note, that the home directory and + shell fields are not based on PATH_MAX, which can be excessively large. + + _POSIX_LOGIN_NAME_MAX + 1 + + sizeof(void *) + + sizeof("4294967295") + 1 + + 2 * (_POSIX_NAME_MAX + 1 + sizeof(void *)) + + sizeof(void *) + +config POSIX_GETPW_R_SIZE_MAX + int "Initial size of getpwnam_r() and getpwuid_r() data buffers" + default 64 + help + This option sets the maximum size of the buffer for getpwnam_r() and getpwuid_r(). + + The default size is derived from the formula below. Note, that the home directory and + shell fields are not based on PATH_MAX, which can be excessively large. + + _POSIX_LOGIN_NAME_MAX + 1 + + sizeof("4294967295") + 1 + + sizeof("4294967295") + 1 + + _POSIX_NAME_MAX + 1 + + _POSIX_NAME_MAX + 1 diff --git a/lib/posix/options/grp.c b/lib/posix/options/grp.c deleted file mode 100644 index 32c840e10164..000000000000 --- a/lib/posix/options/grp.c +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (c) 2024 Meta Platforms - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#include - -#include -#include - -#ifdef CONFIG_POSIX_THREAD_SAFE_FUNCTIONS - -int getgrnam_r(const char *name, struct group *grp, char *buffer, size_t bufsize, - struct group **result) -{ - ARG_UNUSED(name); - ARG_UNUSED(grp); - ARG_UNUSED(buffer); - ARG_UNUSED(bufsize); - ARG_UNUSED(result); - - return ENOSYS; -} - -int getgrgid_r(gid_t gid, struct group *grp, char *buffer, size_t bufsize, struct group **result) -{ - ARG_UNUSED(gid); - ARG_UNUSED(grp); - ARG_UNUSED(buffer); - ARG_UNUSED(bufsize); - ARG_UNUSED(result); - - return ENOSYS; -} - -#endif /* CONFIG_POSIX_THREAD_SAFE_FUNCTIONS */ diff --git a/lib/posix/options/pwd.c b/lib/posix/options/pwd.c deleted file mode 100644 index f806767f3df9..000000000000 --- a/lib/posix/options/pwd.c +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (c) 2024 Meta Platforms - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#include - -#include -#include - -#ifdef CONFIG_POSIX_THREAD_SAFE_FUNCTIONS - -int getpwnam_r(const char *nam, struct passwd *pwd, char *buffer, size_t bufsize, - struct passwd **result) -{ - ARG_UNUSED(nam); - ARG_UNUSED(pwd); - ARG_UNUSED(buffer); - ARG_UNUSED(bufsize); - ARG_UNUSED(result); - - return ENOSYS; -} - -int getpwuid_r(uid_t uid, struct passwd *pwd, char *buffer, size_t bufsize, struct passwd **result) -{ - ARG_UNUSED(uid); - ARG_UNUSED(pwd); - ARG_UNUSED(buffer); - ARG_UNUSED(bufsize); - ARG_UNUSED(result); - - return ENOSYS; -} - -#endif /* CONFIG_POSIX_THREAD_SAFE_FUNCTIONS */ diff --git a/lib/posix/options/system_database_priv.c b/lib/posix/options/system_database_priv.c new file mode 100644 index 000000000000..1ab265d759db --- /dev/null +++ b/lib/posix/options/system_database_priv.c @@ -0,0 +1,272 @@ +/* + * Copyright (c) 2024 Tenstorrent AI ULC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include + +#include +#include +#include + +static int count(const char *s, char c) +{ + int count; + + for (count = 0; *s != '\0'; ++s) { + count += (*s == c) ? 1 : 0; + } + + return count; +} + +int __getgr_r(const char *name, gid_t gid, struct group *grp, char *buffer, size_t bufsize, + struct group **result) +{ + int ret; + int nmemb; + FILE *file; + + if (((name == NULL) && (gid == (gid_t)-1)) || (grp == NULL) || (buffer == NULL) || + (result == NULL)) { + if (result != NULL) { + *result = NULL; + } + return EINVAL; + } + + /* + * Originally, this checked for a bufsize of 0, but newlib will return NULL from fgets when + * bufsize is < 2 + */ + if (bufsize < 2) { + return ERANGE; + } + + file = fopen("/etc/group", "r"); + if (file == NULL) { + return EIO; + } + + while (fgets(buffer, bufsize, file) != NULL) { + char *p = buffer; + char *q; + + if (*p == '\0') { + goto close_erange; + } + + if (*p == '\n') { + continue; + } + + /* name */ + q = strchr(p, ':'); + if (q == NULL) { + goto close_erange; + } + *q = '\0'; + if ((name != NULL) && (strcmp(p, name) != 0)) { + continue; + } + grp->gr_name = p; + p = q + 1; + + /* password */ + q = strchr(p, ':'); + if (q == NULL) { + goto close_erange; + } + *q = '\0'; + p = q + 1; + + /* gid */ + q = strchr(p, ':'); + if (q == NULL) { + goto close_erange; + } + *q = '\0'; + if ((name == NULL) && (atoi(p) != gid)) { + continue; + } + grp->gr_gid = atoi(p); + p = q + 1; + + /* members */ + q = strchr(p, '\n'); + if (q == NULL) { + goto close_erange; + } + *q = '\0'; + + /* count members */ + int min_size; + + nmemb = (p == q) ? 0 : 1 + count(p, ','); + min_size = (q - buffer + 1 + nmemb * sizeof(char *)) + 32; + if (bufsize < min_size) { + goto close_erange; + } + + /* set up member array inside of buffer */ + grp->gr_mem = (char **)(q + 1); + grp->gr_mem = (char **)ROUND_UP((uintptr_t)grp->gr_mem, 16); + grp->gr_mem[nmemb] = NULL; + + for (int i = 0; i < nmemb; ++i) { + char *x = strchr(p, ','); + + grp->gr_mem[i] = p; + if (x == NULL) { + break; + } + *x = '\0'; + p = x + 1; + } + + /* group found \o/ */ + *result = grp; + ret = 0; + goto close_ret; + } + + /* group not found :( )*/ + ret = 0; + *result = NULL; + goto close_ret; + +close_erange: + ret = ERANGE; + +close_ret: + fclose(file); + return ret; +} + +int __getpw_r(const char *name, uid_t uid, struct passwd *pwd, char *buffer, size_t bufsize, + struct passwd **result) +{ + int ret; + FILE *file; + + if (((name == NULL) && (uid == (uid_t)-1)) || (pwd == NULL) || (buffer == NULL) || + (result == NULL)) { + if (result != NULL) { + *result = NULL; + } + return EINVAL; + } + + /* + * Originally, this checked for a bufsize of 0, but newlib will return NULL from fgets when + * bufsize is < 2 + */ + if (bufsize < 2) { + return ERANGE; + } + + file = fopen("/etc/passwd", "r"); + if (file == NULL) { + return EIO; + } + + while (fgets(buffer, bufsize, file) != NULL) { + char *p = buffer; + char *q; + + if (*p == '\0') { + goto close_erange; + } + + if (*p == '\n') { + continue; + } + + /* name */ + q = strchr(p, ':'); + if (q == NULL) { + goto close_erange; + } + *q = '\0'; + if ((name != NULL) && (strcmp(p, name) != 0)) { + continue; + } + pwd->pw_name = p; + p = q + 1; + + /* password */ + q = strchr(p, ':'); + if (q == NULL) { + goto close_erange; + } + *q = '\0'; + p = q + 1; + + /* uid */ + q = strchr(p, ':'); + if (q == NULL) { + goto close_erange; + } + *q = '\0'; + if ((name == NULL) && (atoi(p) != uid)) { + continue; + } + pwd->pw_uid = atoi(p); + p = q + 1; + + /* gid */ + q = strchr(p, ':'); + if (q == NULL) { + goto close_erange; + } + *q = '\0'; + pwd->pw_gid = atoi(p); + p = q + 1; + + /* gecos */ + q = strchr(p, ':'); + if (q == NULL) { + goto close_erange; + } + *q = '\0'; + p = q + 1; + + /* dir */ + q = strchr(p, ':'); + if (q == NULL) { + goto close_erange; + } + *q = '\0'; + pwd->pw_dir = p; + p = q + 1; + + /* shell */ + q = strchr(p, '\n'); + if (q == NULL) { + goto close_erange; + } + *q = '\0'; + pwd->pw_shell = p; + + /* user found \o/ */ + *result = pwd; + ret = 0; + goto close_ret; + } + + /* user not found :( )*/ + ret = 0; + *result = NULL; + goto close_ret; + +close_erange: + ret = ERANGE; + +close_ret: + fclose(file); + return ret; +} diff --git a/lib/posix/options/system_database_priv.h b/lib/posix/options/system_database_priv.h new file mode 100644 index 000000000000..9ec79d58882a --- /dev/null +++ b/lib/posix/options/system_database_priv.h @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2024 Tenstorrent AI ULC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include +#include + +int __getgr_r(const char *name, gid_t gid, struct group *grp, char *buffer, size_t bufsize, + struct group **result); +int __getpw_r(const char *name, uid_t uid, struct passwd *pwd, char *buffer, size_t bufsize, + struct passwd **result); diff --git a/lib/posix/options/system_database_r.c b/lib/posix/options/system_database_r.c new file mode 100644 index 000000000000..3e98143dd7b8 --- /dev/null +++ b/lib/posix/options/system_database_r.c @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2024 Tenstorrent AI ULC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "system_database_priv.h" + +#include +#include +#include +#include + +#include +#include +#include + +int getpwnam_r(const char *name, struct passwd *pwd, char *buffer, size_t bufsize, + struct passwd **result) +{ + return __getpw_r(name, -1, pwd, buffer, bufsize, result); +} + +int getpwuid_r(uid_t uid, struct passwd *pwd, char *buffer, size_t bufsize, struct passwd **result) +{ + return __getpw_r(NULL, uid, pwd, buffer, bufsize, result); +} + +int getgrnam_r(const char *name, struct group *grp, char *buffer, size_t bufsize, + struct group **result) +{ + return __getgr_r(name, -1, grp, buffer, bufsize, result); +} + +int getgrgid_r(gid_t gid, struct group *grp, char *buffer, size_t bufsize, struct group **result) +{ + return __getgr_r(NULL, gid, grp, buffer, bufsize, result); +} From a98d8fba4027c120da48d44c080029006db9d007 Mon Sep 17 00:00:00 2001 From: Chris Friedt Date: Tue, 24 Dec 2024 12:24:15 -0500 Subject: [PATCH 04/13] tests: posix: add POSIX_SYSTEM_DATABASE_R testsuite Add a teststuite to exercise the POSIX_SYSTEM_DATABASE_R Option Group. Signed-off-by: Chris Friedt --- tests/posix/common/src/grp.c | 18 -- tests/posix/common/src/pwd.c | 18 -- tests/posix/fs/src/main.c | 1 + tests/posix/system_database_r/CMakeLists.txt | 10 + tests/posix/system_database_r/app.overlay | 14 + tests/posix/system_database_r/prj.conf | 8 + tests/posix/system_database_r/src/fs.c | 70 +++++ tests/posix/system_database_r/src/main.c | 288 +++++++++++++++++++ tests/posix/system_database_r/testcase.yaml | 25 ++ 9 files changed, 416 insertions(+), 36 deletions(-) delete mode 100644 tests/posix/common/src/grp.c delete mode 100644 tests/posix/common/src/pwd.c create mode 100644 tests/posix/system_database_r/CMakeLists.txt create mode 100644 tests/posix/system_database_r/app.overlay create mode 100644 tests/posix/system_database_r/prj.conf create mode 100644 tests/posix/system_database_r/src/fs.c create mode 100644 tests/posix/system_database_r/src/main.c create mode 100644 tests/posix/system_database_r/testcase.yaml diff --git a/tests/posix/common/src/grp.c b/tests/posix/common/src/grp.c deleted file mode 100644 index 1fce1433bff8..000000000000 --- a/tests/posix/common/src/grp.c +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright (c) 2024 Meta Platforms - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#include -#include - -#include - -ZTEST(grp, test_grp_stubs) -{ - zassert_equal(getgrnam_r(NULL, NULL, NULL, 42, NULL), ENOSYS); - zassert_equal(getgrgid_r(42, NULL, NULL, 42, NULL), ENOSYS); -} - -ZTEST_SUITE(grp, NULL, NULL, NULL, NULL, NULL); diff --git a/tests/posix/common/src/pwd.c b/tests/posix/common/src/pwd.c deleted file mode 100644 index dd473569f0d3..000000000000 --- a/tests/posix/common/src/pwd.c +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright (c) 2024 Meta Platforms - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#include -#include - -#include - -ZTEST(pwd, test_pwd_stubs) -{ - zassert_equal(getpwnam_r(NULL, NULL, NULL, 42, NULL), ENOSYS); - zassert_equal(getpwuid_r(42, NULL, NULL, 42, NULL), ENOSYS); -} - -ZTEST_SUITE(pwd, NULL, NULL, NULL, NULL, NULL); diff --git a/tests/posix/fs/src/main.c b/tests/posix/fs/src/main.c index 6a124d253732..9c59073c2282 100644 --- a/tests/posix/fs/src/main.c +++ b/tests/posix/fs/src/main.c @@ -4,6 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ +#include #include "test_fs.h" ZTEST_SUITE(posix_fs_test, NULL, test_mount, NULL, NULL, test_unmount); diff --git a/tests/posix/system_database_r/CMakeLists.txt b/tests/posix/system_database_r/CMakeLists.txt new file mode 100644 index 000000000000..e38c61e48946 --- /dev/null +++ b/tests/posix/system_database_r/CMakeLists.txt @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(posix_system_database_r) + +FILE(GLOB app_sources src/*.c) +target_sources(app PRIVATE ${app_sources}) + +target_compile_options(app PRIVATE -U_POSIX_C_SOURCE -D_POSIX_C_SOURCE=200809L) diff --git a/tests/posix/system_database_r/app.overlay b/tests/posix/system_database_r/app.overlay new file mode 100644 index 000000000000..87ae21b1e647 --- /dev/null +++ b/tests/posix/system_database_r/app.overlay @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/ { + ramdisk0 { + compatible = "zephyr,ram-disk"; + disk-name = "RAM"; + sector-size = <512>; + sector-count = <160>; + }; +}; diff --git a/tests/posix/system_database_r/prj.conf b/tests/posix/system_database_r/prj.conf new file mode 100644 index 000000000000..47f9fbb4e5b0 --- /dev/null +++ b/tests/posix/system_database_r/prj.conf @@ -0,0 +1,8 @@ +CONFIG_POSIX_API=y +CONFIG_ZTEST=y + +CONFIG_POSIX_AEP_CHOICE_BASE=y +CONFIG_POSIX_SYSTEM_DATABASE_R=y +CONFIG_FAT_FILESYSTEM_ELM=y + +CONFIG_MAIN_STACK_SIZE=4096 diff --git a/tests/posix/system_database_r/src/fs.c b/tests/posix/system_database_r/src/fs.c new file mode 100644 index 000000000000..9cf220f52c0b --- /dev/null +++ b/tests/posix/system_database_r/src/fs.c @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2024 Tenstorrent AI ULC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include +#include +#include +#include + +static FATFS _fs; + +static struct fs_mount_t _mnt = { + .type = FS_FATFS, + .mnt_point = "/", + .fs_data = &_fs, +}; + +struct _entry { + const char *const name; + const char *const data; +}; + +static const struct _entry _data[] = { + {.name = "/etc/passwd", + .data = "user:x:1000:1000:user:/home/user:/bin/sh\nroot:x:0:0:root:/root:/bin/sh\n"}, + {.name = "/etc/group", .data = "user:x:1000:staff,admin\nroot:x:0:\n"}, +}; + +void *setup(void) +{ + int ret; + + memset(&_fs, 0, sizeof(_fs)); + + ret = fs_mount(&_mnt); + zassert_ok(ret, "mount failed: %d", ret); + + ret = fs_mkdir("/etc"); + zassert_ok(ret, "mkdir failed: %d", ret); + + ARRAY_FOR_EACH_PTR(_data, entry) { + int len; + struct fs_file_t zfp; + + fs_file_t_init(&zfp); + + ret = fs_open(&zfp, entry->name, FS_O_CREATE | FS_O_RDWR | FS_O_TRUNC); + zassert_true(ret >= 0, "open failed: %d", ret); + + len = strlen(entry->data); + ret = fs_write(&zfp, entry->data, len); + zassert_equal(ret, len, "%s returned %d instead of %d", "write", ret, len); + + ret = fs_close(&zfp); + zassert_ok(ret, "%s returned %d instead of %d", "close", ret, len); + }; + + return &_mnt; +} + +void teardown(void *arg) +{ + struct fs_mount_t *const mnt = arg; + + (void)fs_unmount(mnt); +} diff --git a/tests/posix/system_database_r/src/main.c b/tests/posix/system_database_r/src/main.c new file mode 100644 index 000000000000..9efea6c463aa --- /dev/null +++ b/tests/posix/system_database_r/src/main.c @@ -0,0 +1,288 @@ +/* + * Copyright (c) 2024 Tenstorrent AI ULC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include + +static char buf[CONFIG_POSIX_GETGR_R_SIZE_MAX]; + +ZTEST(posix_system_database_r, test_getpwnam_r) +{ + struct passwd pwd; + struct passwd *result; + + { + /* degenerate cases */ + zexpect_not_ok(getpwnam_r(NULL, NULL, NULL, 0, NULL)); + zexpect_not_ok(getpwnam_r(NULL, NULL, NULL, 0, &result)); + zexpect_not_ok(getpwnam_r(NULL, NULL, NULL, sizeof(buf), NULL)); + zexpect_not_ok(getpwnam_r(NULL, NULL, NULL, sizeof(buf), &result)); + zexpect_not_ok(getpwnam_r(NULL, NULL, buf, 0, NULL)); + zexpect_not_ok(getpwnam_r(NULL, NULL, buf, 0, &result)); + zexpect_not_ok(getpwnam_r(NULL, NULL, buf, sizeof(buf), NULL)); + zexpect_not_ok(getpwnam_r(NULL, NULL, buf, sizeof(buf), &result)); + zexpect_not_ok(getpwnam_r(NULL, &pwd, NULL, 0, NULL)); + zexpect_not_ok(getpwnam_r(NULL, &pwd, NULL, 0, &result)); + zexpect_not_ok(getpwnam_r(NULL, &pwd, NULL, sizeof(buf), NULL)); + zexpect_not_ok(getpwnam_r(NULL, &pwd, NULL, sizeof(buf), &result)); + zexpect_not_ok(getpwnam_r(NULL, &pwd, buf, 0, NULL)); + zexpect_not_ok(getpwnam_r(NULL, &pwd, buf, 0, &result)); + zexpect_not_ok(getpwnam_r(NULL, &pwd, buf, sizeof(buf), NULL)); + zexpect_not_ok(getpwnam_r(NULL, &pwd, buf, sizeof(buf), &result)); + zexpect_not_ok(getpwnam_r("root", NULL, NULL, 0, NULL)); + zexpect_not_ok(getpwnam_r("root", NULL, NULL, 0, &result)); + zexpect_not_ok(getpwnam_r("root", NULL, NULL, sizeof(buf), NULL)); + zexpect_not_ok(getpwnam_r("root", NULL, NULL, sizeof(buf), &result)); + zexpect_not_ok(getpwnam_r("root", NULL, buf, 0, NULL)); + zexpect_not_ok(getpwnam_r("root", NULL, buf, 0, &result)); + zexpect_not_ok(getpwnam_r("root", NULL, buf, sizeof(buf), NULL)); + zexpect_not_ok(getpwnam_r("root", NULL, buf, sizeof(buf), &result)); + zexpect_not_ok(getpwnam_r("root", &pwd, NULL, 0, NULL)); + zexpect_not_ok(getpwnam_r("root", &pwd, NULL, 0, &result)); + zexpect_not_ok(getpwnam_r("root", &pwd, NULL, sizeof(buf), NULL)); + zexpect_not_ok(getpwnam_r("root", &pwd, NULL, sizeof(buf), &result)); + zexpect_not_ok(getpwnam_r("root", &pwd, buf, 0, NULL)); + zexpect_not_ok(getpwnam_r("root", &pwd, buf, 0, &result)); + zexpect_not_ok(getpwnam_r("root", &pwd, buf, sizeof(buf), NULL)); + + /* buffer is not large enough */ + zexpect_equal(getpwnam_r("root", &pwd, buf, 1, &result), ERANGE); + + /* user is not found in /etc/passwd */ + result = (struct passwd *)0x42; + zexpect_ok(getpwnam_r("nobody", &pwd, buf, sizeof(buf), &result)); + zexpect_equal(result, NULL); + } + + zexpect_ok(getpwnam_r("root", &pwd, buf, sizeof(buf), &result)); + zexpect_equal(result, &pwd); + zexpect_str_equal(pwd.pw_name, "root"); + zexpect_equal(pwd.pw_uid, 0); + zexpect_equal(pwd.pw_gid, 0); + zexpect_str_equal(pwd.pw_dir, "/root"); + zexpect_str_equal(pwd.pw_shell, "/bin/sh"); + + zexpect_ok(getpwnam_r("user", &pwd, buf, sizeof(buf), &result)); + zexpect_equal(result, &pwd); + zexpect_str_equal(pwd.pw_name, "user"); + zexpect_equal(pwd.pw_uid, 1000); + zexpect_equal(pwd.pw_gid, 1000); + zexpect_str_equal(pwd.pw_dir, "/home/user"); + zexpect_str_equal(pwd.pw_shell, "/bin/sh"); +} + +ZTEST(posix_system_database_r, test_getpwuid_r) +{ + struct passwd pwd; + struct passwd *result; + + { + /* degenerate cases */ + zexpect_not_ok(getpwuid_r(-1, NULL, NULL, 0, NULL)); + zexpect_not_ok(getpwuid_r(-1, NULL, NULL, 0, &result)); + zexpect_not_ok(getpwuid_r(-1, NULL, NULL, sizeof(buf), NULL)); + zexpect_not_ok(getpwuid_r(-1, NULL, NULL, sizeof(buf), &result)); + zexpect_not_ok(getpwuid_r(-1, NULL, buf, 0, NULL)); + zexpect_not_ok(getpwuid_r(-1, NULL, buf, 0, &result)); + zexpect_not_ok(getpwuid_r(-1, NULL, buf, sizeof(buf), NULL)); + zexpect_not_ok(getpwuid_r(-1, NULL, buf, sizeof(buf), &result)); + zexpect_not_ok(getpwuid_r(-1, &pwd, NULL, 0, NULL)); + zexpect_not_ok(getpwuid_r(-1, &pwd, NULL, 0, &result)); + zexpect_not_ok(getpwuid_r(-1, &pwd, NULL, sizeof(buf), NULL)); + zexpect_not_ok(getpwuid_r(-1, &pwd, NULL, sizeof(buf), &result)); + zexpect_not_ok(getpwuid_r(-1, &pwd, buf, 0, NULL)); + zexpect_not_ok(getpwuid_r(-1, &pwd, buf, 0, &result)); + zexpect_not_ok(getpwuid_r(-1, &pwd, buf, sizeof(buf), NULL)); + zexpect_not_ok(getpwuid_r(-1, &pwd, buf, sizeof(buf), &result)); + zexpect_not_ok(getpwuid_r(0, NULL, NULL, 0, NULL)); + zexpect_not_ok(getpwuid_r(0, NULL, NULL, 0, &result)); + zexpect_not_ok(getpwuid_r(0, NULL, NULL, sizeof(buf), NULL)); + zexpect_not_ok(getpwuid_r(0, NULL, NULL, sizeof(buf), &result)); + zexpect_not_ok(getpwuid_r(0, NULL, buf, 0, NULL)); + zexpect_not_ok(getpwuid_r(0, NULL, buf, 0, &result)); + zexpect_not_ok(getpwuid_r(0, NULL, buf, sizeof(buf), NULL)); + zexpect_not_ok(getpwuid_r(0, NULL, buf, sizeof(buf), &result)); + zexpect_not_ok(getpwuid_r(0, &pwd, NULL, 0, NULL)); + zexpect_not_ok(getpwuid_r(0, &pwd, NULL, 0, &result)); + zexpect_not_ok(getpwuid_r(0, &pwd, NULL, sizeof(buf), NULL)); + zexpect_not_ok(getpwuid_r(0, &pwd, NULL, sizeof(buf), &result)); + zexpect_not_ok(getpwuid_r(0, &pwd, buf, 0, NULL)); + zexpect_not_ok(getpwuid_r(0, &pwd, buf, 0, &result)); + zexpect_not_ok(getpwuid_r(0, &pwd, buf, sizeof(buf), NULL)); + + /* buffer is not large enough */ + zexpect_equal(getpwuid_r(0, &pwd, buf, 1, &result), ERANGE); + + /* user is not found in /etc/passwd */ + result = (struct passwd *)0x42; + zexpect_ok(getpwuid_r(1001, &pwd, buf, sizeof(buf), &result)); + zexpect_equal(result, NULL); + } + + zexpect_ok(getpwuid_r(0, &pwd, buf, sizeof(buf), &result)); + zexpect_equal(result, &pwd); + zexpect_str_equal(pwd.pw_name, "root"); + zexpect_equal(pwd.pw_uid, 0); + zexpect_equal(pwd.pw_gid, 0); + zexpect_str_equal(pwd.pw_dir, "/root"); + zexpect_str_equal(pwd.pw_shell, "/bin/sh"); + + zexpect_ok(getpwuid_r(1000, &pwd, buf, sizeof(buf), &result)); + zexpect_equal(result, &pwd); + zexpect_str_equal(pwd.pw_name, "user"); + zexpect_equal(pwd.pw_uid, 1000); + zexpect_equal(pwd.pw_gid, 1000); + zexpect_str_equal(pwd.pw_dir, "/home/user"); + zexpect_str_equal(pwd.pw_shell, "/bin/sh"); +} + +static const char *const members[] = { + "staff", + "admin", +}; + +ZTEST(posix_system_database_r, test_getgrnam_r) +{ + struct group grp; + struct group *result; + + { + /* degenerate cases */ + zexpect_not_ok(getgrnam_r(NULL, NULL, NULL, 0, NULL)); + zexpect_not_ok(getgrnam_r(NULL, NULL, NULL, 0, &result)); + zexpect_not_ok(getgrnam_r(NULL, NULL, NULL, sizeof(buf), NULL)); + zexpect_not_ok(getgrnam_r(NULL, NULL, NULL, sizeof(buf), &result)); + zexpect_not_ok(getgrnam_r(NULL, NULL, buf, 0, NULL)); + zexpect_not_ok(getgrnam_r(NULL, NULL, buf, 0, &result)); + zexpect_not_ok(getgrnam_r(NULL, NULL, buf, sizeof(buf), NULL)); + zexpect_not_ok(getgrnam_r(NULL, NULL, buf, sizeof(buf), &result)); + zexpect_not_ok(getgrnam_r(NULL, &grp, NULL, 0, NULL)); + zexpect_not_ok(getgrnam_r(NULL, &grp, NULL, 0, &result)); + zexpect_not_ok(getgrnam_r(NULL, &grp, NULL, sizeof(buf), NULL)); + zexpect_not_ok(getgrnam_r(NULL, &grp, NULL, sizeof(buf), &result)); + zexpect_not_ok(getgrnam_r(NULL, &grp, buf, 0, NULL)); + zexpect_not_ok(getgrnam_r(NULL, &grp, buf, 0, &result)); + zexpect_not_ok(getgrnam_r(NULL, &grp, buf, sizeof(buf), NULL)); + zexpect_not_ok(getgrnam_r(NULL, &grp, buf, sizeof(buf), &result)); + zexpect_not_ok(getgrnam_r("root", NULL, NULL, 0, NULL)); + zexpect_not_ok(getgrnam_r("root", NULL, NULL, 0, &result)); + zexpect_not_ok(getgrnam_r("root", NULL, NULL, sizeof(buf), NULL)); + zexpect_not_ok(getgrnam_r("root", NULL, NULL, sizeof(buf), &result)); + zexpect_not_ok(getgrnam_r("root", NULL, buf, 0, NULL)); + zexpect_not_ok(getgrnam_r("root", NULL, buf, 0, &result)); + zexpect_not_ok(getgrnam_r("root", NULL, buf, sizeof(buf), NULL)); + zexpect_not_ok(getgrnam_r("root", NULL, buf, sizeof(buf), &result)); + zexpect_not_ok(getgrnam_r("root", &grp, NULL, 0, NULL)); + zexpect_not_ok(getgrnam_r("root", &grp, NULL, 0, &result)); + zexpect_not_ok(getgrnam_r("root", &grp, NULL, sizeof(buf), NULL)); + zexpect_not_ok(getgrnam_r("root", &grp, NULL, sizeof(buf), &result)); + zexpect_not_ok(getgrnam_r("root", &grp, buf, 0, NULL)); + zexpect_not_ok(getgrnam_r("root", &grp, buf, 0, &result)); + zexpect_not_ok(getgrnam_r("root", &grp, buf, sizeof(buf), NULL)); + + /* buffer is not large enough */ + zexpect_equal(getgrnam_r("root", &grp, buf, 1, &result), ERANGE); + + /* group is not found in /etc/group */ + result = (struct group *)0x42; + zexpect_ok(getgrnam_r("nobody", &grp, buf, sizeof(buf), &result)); + zexpect_equal(result, NULL); + } + + zexpect_ok(getgrnam_r("root", &grp, buf, sizeof(buf), &result)); + zexpect_equal(result, &grp); + zexpect_str_equal(grp.gr_name, "root"); + zexpect_equal(grp.gr_gid, 0); + zassert_within((uintptr_t)grp.gr_mem, (uintptr_t)buf, sizeof(buf)); + zexpect_equal(grp.gr_mem[0], NULL); + + zexpect_ok(getgrnam_r("user", &grp, buf, sizeof(buf), &result)); + zexpect_equal(result, &grp); + zexpect_str_equal(grp.gr_name, "user"); + zexpect_equal(grp.gr_gid, 1000); + zassert_within((uintptr_t)grp.gr_mem, (uintptr_t)buf, sizeof(buf)); + ARRAY_FOR_EACH(members, i) { + zexpect_str_equal(grp.gr_mem[i], members[i], + "members[%d] (%s) does not match gr.gr_mem[%d] (%s)", i, + members[i], i, grp.gr_mem[i]); + } + zexpect_equal(grp.gr_mem[2], NULL); +} + +ZTEST(posix_system_database_r, test_getgrgid_r) +{ + struct group grp; + struct group *result; + + { + /* degenerate cases */ + zexpect_not_ok(getgrgid_r(-1, NULL, NULL, 0, NULL)); + zexpect_not_ok(getgrgid_r(-1, NULL, NULL, 0, &result)); + zexpect_not_ok(getgrgid_r(-1, NULL, NULL, sizeof(buf), NULL)); + zexpect_not_ok(getgrgid_r(-1, NULL, NULL, sizeof(buf), &result)); + zexpect_not_ok(getgrgid_r(-1, NULL, buf, 0, NULL)); + zexpect_not_ok(getgrgid_r(-1, NULL, buf, 0, &result)); + zexpect_not_ok(getgrgid_r(-1, NULL, buf, sizeof(buf), NULL)); + zexpect_not_ok(getgrgid_r(-1, NULL, buf, sizeof(buf), &result)); + zexpect_not_ok(getgrgid_r(-1, &grp, NULL, 0, NULL)); + zexpect_not_ok(getgrgid_r(-1, &grp, NULL, 0, &result)); + zexpect_not_ok(getgrgid_r(-1, &grp, NULL, sizeof(buf), NULL)); + zexpect_not_ok(getgrgid_r(-1, &grp, NULL, sizeof(buf), &result)); + zexpect_not_ok(getgrgid_r(-1, &grp, buf, 0, NULL)); + zexpect_not_ok(getgrgid_r(-1, &grp, buf, 0, &result)); + zexpect_not_ok(getgrgid_r(-1, &grp, buf, sizeof(buf), NULL)); + zexpect_not_ok(getgrgid_r(-1, &grp, buf, sizeof(buf), &result)); + zexpect_not_ok(getgrgid_r(0, NULL, NULL, 0, NULL)); + zexpect_not_ok(getgrgid_r(0, NULL, NULL, 0, &result)); + zexpect_not_ok(getgrgid_r(0, NULL, NULL, sizeof(buf), NULL)); + zexpect_not_ok(getgrgid_r(0, NULL, NULL, sizeof(buf), &result)); + zexpect_not_ok(getgrgid_r(0, NULL, buf, 0, NULL)); + zexpect_not_ok(getgrgid_r(0, NULL, buf, 0, &result)); + zexpect_not_ok(getgrgid_r(0, NULL, buf, sizeof(buf), NULL)); + zexpect_not_ok(getgrgid_r(0, NULL, buf, sizeof(buf), &result)); + zexpect_not_ok(getgrgid_r(0, &grp, NULL, 0, NULL)); + zexpect_not_ok(getgrgid_r(0, &grp, NULL, 0, &result)); + zexpect_not_ok(getgrgid_r(0, &grp, NULL, sizeof(buf), NULL)); + zexpect_not_ok(getgrgid_r(0, &grp, NULL, sizeof(buf), &result)); + zexpect_not_ok(getgrgid_r(0, &grp, buf, 0, NULL)); + zexpect_not_ok(getgrgid_r(0, &grp, buf, 0, &result)); + zexpect_not_ok(getgrgid_r(0, &grp, buf, sizeof(buf), NULL)); + + /* buffer is not large enough */ + zexpect_equal(getgrgid_r(0, &grp, buf, 1, &result), ERANGE); + + /* group is not found in /etc/group */ + result = (struct group *)0x42; + zexpect_ok(getgrgid_r(1001, &grp, buf, sizeof(buf), &result)); + zexpect_equal(result, NULL); + } + + zexpect_ok(getgrgid_r(0, &grp, buf, sizeof(buf), &result)); + zexpect_equal(result, &grp); + zexpect_str_equal(grp.gr_name, "root"); + zexpect_equal(grp.gr_gid, 0); + zassert_within((uintptr_t)grp.gr_mem, (uintptr_t)buf, sizeof(buf)); + zexpect_equal(grp.gr_mem[0], NULL); + + zexpect_ok(getgrgid_r(1000, &grp, buf, sizeof(buf), &result)); + zexpect_equal(result, &grp); + zexpect_str_equal(grp.gr_name, "user"); + zexpect_equal(grp.gr_gid, 1000); + zassert_within((uintptr_t)grp.gr_mem, (uintptr_t)buf, sizeof(buf)); + ARRAY_FOR_EACH(members, i) { + zexpect_str_equal(grp.gr_mem[i], members[i], + "members[%d] (%s) does not match gr.gr_mem[%d] (%s)", i, + members[i], i, grp.gr_mem[i]); + } + zexpect_equal(grp.gr_mem[2], NULL); +} + +void *setup(void); +void teardown(void *arg); + +ZTEST_SUITE(posix_system_database_r, NULL, setup, NULL, NULL, teardown); diff --git a/tests/posix/system_database_r/testcase.yaml b/tests/posix/system_database_r/testcase.yaml new file mode 100644 index 000000000000..553e593058b2 --- /dev/null +++ b/tests/posix/system_database_r/testcase.yaml @@ -0,0 +1,25 @@ +common: + filter: not CONFIG_NATIVE_LIBC + tags: + - posix + - posix_system_database_r + # 1 tier0 platform per supported architecture + platform_key: + - arch + - simulation + min_flash: 64 + min_ram: 32 +tests: + portability.posix.posix_system_database_r: {} + portability.posix.posix_system_database_r.minimal: + extra_configs: + - CONFIG_MINIMAL_LIBC=y + portability.posix.posix_system_database_r.newlib: + filter: TOOLCHAIN_HAS_NEWLIB == 1 + extra_configs: + - CONFIG_NEWLIB_LIBC=y + portability.posix.posix_system_database_r.picolibc: + tags: picolibc + filter: CONFIG_PICOLIBC_SUPPORTED + extra_configs: + - CONFIG_PICOLIBC=y From f3585ff07bfddff05571ea2ed85101c7982c63cd Mon Sep 17 00:00:00 2001 From: Chris Friedt Date: Sun, 5 Jan 2025 22:04:10 -0500 Subject: [PATCH 05/13] doc: posix: options: mark POSIX_SYSTEM_DATABASE_R as supported Mark the POSIX_SYSTEM_DATABASE_R Option Group as supported. This Option Group is required for all conformant implementations as part of the _POSIX_THREAD_SAFE_FUNCTIONS Option. Signed-off-by: Chris Friedt --- .../portability/posix/option_groups/index.rst | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/doc/services/portability/posix/option_groups/index.rst b/doc/services/portability/posix/option_groups/index.rst index 473982e4dbd0..97d124372c4a 100644 --- a/doc/services/portability/posix/option_groups/index.rst +++ b/doc/services/portability/posix/option_groups/index.rst @@ -558,6 +558,22 @@ Enable this option group with :kconfig:option:`CONFIG_POSIX_SPIN_LOCKS`. pthread_spin_trylock(),yes pthread_spin_unlock(),yes +.. _posix_option_group_system_database_r: + +POSIX_SYSTEM_DATABASE_R ++++++++++++++++++++++++ + +Enable this option group with :kconfig:option:`CONFIG_POSIX_SYSTEM_DATABASE_R`. + +.. csv-table:: POSIX_SYSTEM_DATABASE_R + :header: API, Supported + :widths: 50,10 + + getgrgid_r(),yes + getgrnam_r(),yes + getpwnam_r(),yes + getpwuid_r(),yes + .. _posix_option_group_threads_base: POSIX_THREADS_BASE From 32bbbca93391439a519b2756e584d859ead994c7 Mon Sep 17 00:00:00 2001 From: Chris Friedt Date: Tue, 24 Dec 2024 12:41:39 -0500 Subject: [PATCH 06/13] posix: options: implement POSIX_SYSTEM_DATABASE Option Group Provide an implementation for the POSIX_SYSTEM_DATABASE Option Group. Signed-off-by: Chris Friedt --- lib/posix/options/CMakeLists.txt | 7 ++ lib/posix/options/Kconfig | 1 + lib/posix/options/Kconfig.system_database | 13 ++++ lib/posix/options/system_database.c | 85 +++++++++++++++++++++++ 4 files changed, 106 insertions(+) create mode 100644 lib/posix/options/Kconfig.system_database create mode 100644 lib/posix/options/system_database.c diff --git a/lib/posix/options/CMakeLists.txt b/lib/posix/options/CMakeLists.txt index ebe6e2901079..2a663651fcc3 100644 --- a/lib/posix/options/CMakeLists.txt +++ b/lib/posix/options/CMakeLists.txt @@ -121,6 +121,13 @@ if (NOT CONFIG_TC_PROVIDES_POSIX_SPIN_LOCKS) zephyr_library_sources_ifdef(CONFIG_POSIX_SPIN_LOCKS spinlock.c) endif() +if (NOT CONFIG_TC_PROVIDES_POSIX_SYSTEM_DATABASE) + zephyr_library_sources_ifdef(CONFIG_POSIX_SYSTEM_DATABASE + system_database_priv.c + system_database.c + ) +endif() + if (NOT CONFIG_TC_PROVIDES_POSIX_SYSTEM_DATABASE_R) zephyr_library_sources_ifdef(CONFIG_POSIX_SYSTEM_DATABASE_R system_database_priv.c diff --git a/lib/posix/options/Kconfig b/lib/posix/options/Kconfig index 950fa98730d6..f526d9bce73b 100644 --- a/lib/posix/options/Kconfig +++ b/lib/posix/options/Kconfig @@ -28,6 +28,7 @@ rsource "Kconfig.semaphore" rsource "Kconfig.signal" rsource "Kconfig.spinlock" rsource "Kconfig.sync_io" +rsource "Kconfig.system_database" rsource "Kconfig.system_database_r" rsource "Kconfig.timer" diff --git a/lib/posix/options/Kconfig.system_database b/lib/posix/options/Kconfig.system_database new file mode 100644 index 000000000000..5b6c3b505784 --- /dev/null +++ b/lib/posix/options/Kconfig.system_database @@ -0,0 +1,13 @@ +# Copyright (c) 2024 Tenstorrent AI ULC +# +# SPDX-License-Identifier: Apache-2.0 + +config POSIX_SYSTEM_DATABASE + bool "POSIX System Database" + select POSIX_SYSTEM_DATABASE_R + help + Select 'y' here, and the system will support getgrgid(), getgrnam(), getpwnam(), and + getpwuid(). + + For more information, please see + https://pubs.opengroup.org/onlinepubs/9699919799/xrat/V4_subprofiles.html diff --git a/lib/posix/options/system_database.c b/lib/posix/options/system_database.c new file mode 100644 index 000000000000..dc64779b6ba3 --- /dev/null +++ b/lib/posix/options/system_database.c @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2024 Tenstorrent AI ULC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "system_database_priv.h" + +#include +#include +#include + +#include +#include +#include + +static char gr_line_buf[CONFIG_POSIX_GETGR_R_SIZE_MAX]; +static char pw_line_buf[CONFIG_POSIX_GETPW_R_SIZE_MAX]; + +static struct group gr; +static struct passwd pw; + +struct group *getgrgid(gid_t gid) +{ + int ret; + struct group *result; + + ret = __getgr_r(NULL, gid, &gr, gr_line_buf, sizeof(gr_line_buf), &result); + if (ret != 0) { + errno = ret; + return NULL; + } + + return result; +} + +struct group *getgrnam(const char *name) +{ + int ret; + struct group *result; + + if (name == NULL) { + return NULL; + } + + ret = __getgr_r(name, -1, &gr, gr_line_buf, sizeof(gr_line_buf), &result); + if (ret != 0) { + errno = ret; + return NULL; + } + + return result; +} + +struct passwd *getpwnam(const char *name) +{ + int ret; + struct passwd *result; + + if (name == NULL) { + return NULL; + } + + ret = __getpw_r(name, -1, &pw, pw_line_buf, sizeof(pw_line_buf), &result); + if (ret != 0) { + errno = ret; + return NULL; + } + + return result; +} + +struct passwd *getpwuid(uid_t uid) +{ + int ret; + struct passwd *result; + + ret = __getpw_r(NULL, uid, &pw, pw_line_buf, sizeof(pw_line_buf), &result); + if (ret != 0) { + errno = ret; + return NULL; + } + + return result; +} From a93e5e1937237a3c48a4005ca09f08b4f5b022f8 Mon Sep 17 00:00:00 2001 From: Chris Friedt Date: Sat, 28 Dec 2024 13:08:11 -0500 Subject: [PATCH 07/13] tests: posix: add testsuite for POSIX_SYSTEM_DATABASE Add a testsuite for the POSIX_SYSTEM_DATABASE Option Group Signed-off-by: Chris Friedt --- tests/posix/system_database/CMakeLists.txt | 12 ++ tests/posix/system_database/app.overlay | 14 ++ tests/posix/system_database/prj.conf | 8 ++ tests/posix/system_database/src/main.c | 148 +++++++++++++++++++++ tests/posix/system_database/testcase.yaml | 25 ++++ 5 files changed, 207 insertions(+) create mode 100644 tests/posix/system_database/CMakeLists.txt create mode 100644 tests/posix/system_database/app.overlay create mode 100644 tests/posix/system_database/prj.conf create mode 100644 tests/posix/system_database/src/main.c create mode 100644 tests/posix/system_database/testcase.yaml diff --git a/tests/posix/system_database/CMakeLists.txt b/tests/posix/system_database/CMakeLists.txt new file mode 100644 index 000000000000..37d08f6a3b93 --- /dev/null +++ b/tests/posix/system_database/CMakeLists.txt @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(posix_system_database) + +target_sources(app PRIVATE + src/main.c + ../system_database_r/src/fs.c +) + +target_compile_options(app PRIVATE -U_POSIX_C_SOURCE -D_POSIX_C_SOURCE=200809L) diff --git a/tests/posix/system_database/app.overlay b/tests/posix/system_database/app.overlay new file mode 100644 index 000000000000..87ae21b1e647 --- /dev/null +++ b/tests/posix/system_database/app.overlay @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/ { + ramdisk0 { + compatible = "zephyr,ram-disk"; + disk-name = "RAM"; + sector-size = <512>; + sector-count = <160>; + }; +}; diff --git a/tests/posix/system_database/prj.conf b/tests/posix/system_database/prj.conf new file mode 100644 index 000000000000..bf6eff4369b6 --- /dev/null +++ b/tests/posix/system_database/prj.conf @@ -0,0 +1,8 @@ +CONFIG_POSIX_API=y +CONFIG_ZTEST=y + +CONFIG_POSIX_AEP_CHOICE_BASE=y +CONFIG_POSIX_SYSTEM_DATABASE=y +CONFIG_FAT_FILESYSTEM_ELM=y + +CONFIG_MAIN_STACK_SIZE=4096 diff --git a/tests/posix/system_database/src/main.c b/tests/posix/system_database/src/main.c new file mode 100644 index 000000000000..1a5968b27117 --- /dev/null +++ b/tests/posix/system_database/src/main.c @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2024 Tenstorrent AI ULC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include + +ZTEST(posix_system_database, test_getpwnam) +{ + struct passwd *result; + + { + /* degenerate cases */ + errno = 0; + zexpect_is_null(getpwnam(NULL)); + zexpect_equal(errno, 0); + + /* user is not found in /etc/passwd */ + errno = 0; + zexpect_is_null(getpwnam("nobody")); + zexpect_equal(errno, 0, "expected errno to be zero, not %d", errno); + } + + result = getpwnam("root"); + zassert_not_null(result, "getpwnam(\"root\") failed: %d", errno); + zexpect_str_equal(result->pw_name, "root"); + zexpect_equal(result->pw_uid, 0); + zexpect_equal(result->pw_gid, 0); + zexpect_str_equal(result->pw_dir, "/root"); + zexpect_str_equal(result->pw_shell, "/bin/sh"); + + result = getpwnam("user"); + zassert_not_null(result, "getpwnam(\"user\") failed: %d", errno); + zexpect_str_equal(result->pw_name, "user"); + zexpect_equal(result->pw_uid, 1000); + zexpect_equal(result->pw_gid, 1000); + zexpect_str_equal(result->pw_dir, "/home/user"); + zexpect_str_equal(result->pw_shell, "/bin/sh"); +} + +ZTEST(posix_system_database, test_getpwuid) +{ + struct passwd *result; + + { + /* degenerate cases */ + + /* user is not found in /etc/passwd */ + errno = 0; + zexpect_is_null(getpwuid(1001)); + zexpect_equal(errno, 0, "expected errno to be zero, not %d", errno); + } + + result = getpwuid(0); + zassert_not_null(result, "getpwuid(0) failed: %d", errno); + zexpect_str_equal(result->pw_name, "root"); + zexpect_equal(result->pw_uid, 0); + zexpect_equal(result->pw_gid, 0); + zexpect_str_equal(result->pw_dir, "/root"); + zexpect_str_equal(result->pw_shell, "/bin/sh"); + + result = getpwuid(1000); + zassert_not_null(result, "getpwuid(0) failed: %d", errno); + zexpect_str_equal(result->pw_name, "user"); + zexpect_equal(result->pw_uid, 1000); + zexpect_equal(result->pw_gid, 1000); + zexpect_str_equal(result->pw_dir, "/home/user"); + zexpect_str_equal(result->pw_shell, "/bin/sh"); +} + +static const char *const members[] = { + "staff", + "admin", +}; + +ZTEST(posix_system_database, test_getgrnam) +{ + struct group *result; + + { + /* degenerate cases */ + errno = 0; + zexpect_is_null(getgrnam(NULL)); + zexpect_equal(errno, 0, "expected errno to be zero, not %d", errno); + + /* group is not found in /etc/group */ + errno = 0; + zexpect_is_null(getgrnam("nobody")); + zexpect_equal(errno, 0, "expected errno to be zero, not %d", errno); + } + + result = getgrnam("root"); + zassert_not_null(result, "getgrnam(\"root\") failed: %d", errno); + zexpect_str_equal(result->gr_name, "root"); + zexpect_equal(result->gr_gid, 0); + zexpect_equal(result->gr_mem[0], NULL); + + result = getgrnam("user"); + zassert_not_null(result, "getgrnam(\"user\") failed: %d", errno); + zexpect_str_equal(result->gr_name, "user"); + zexpect_equal(result->gr_gid, 1000); + ARRAY_FOR_EACH(members, i) { + zexpect_str_equal(result->gr_mem[i], members[i], + "members[%d] (%s) does not match gr.gr_mem[%d] (%s)", i, + members[i], i, result->gr_mem[i]); + } + zexpect_equal(result->gr_mem[2], NULL); +} + +ZTEST(posix_system_database, test_getgrgid) +{ + struct group *result; + + { + /* degenerate cases */ + + /* group is not found in /etc/group */ + errno = 0; + zexpect_is_null(getgrgid(1001)); + zexpect_equal(errno, 0, "expected errno to be zero, not %d", errno); + } + + result = getgrgid(0); + zassert_not_null(result, "getgrgid(0) failed: %d", errno); + zexpect_str_equal(result->gr_name, "root"); + zexpect_equal(result->gr_gid, 0); + zexpect_equal(result->gr_mem[0], NULL); + + result = getgrgid(1000); + zassert_not_null(result, "getgrgid(1000) failed: %d", errno); + zexpect_str_equal(result->gr_name, "user"); + zexpect_equal(result->gr_gid, 1000); + ARRAY_FOR_EACH(members, i) { + zexpect_str_equal(result->gr_mem[i], members[i], + "members[%d] (%s) does not match gr.gr_mem[%d] (%s)", i, + members[i], i, result->gr_mem[i]); + } + zexpect_equal(result->gr_mem[2], NULL); +} + +void *setup(void); +void teardown(void *arg); + +ZTEST_SUITE(posix_system_database, NULL, setup, NULL, NULL, teardown); diff --git a/tests/posix/system_database/testcase.yaml b/tests/posix/system_database/testcase.yaml new file mode 100644 index 000000000000..2b1cab6b031b --- /dev/null +++ b/tests/posix/system_database/testcase.yaml @@ -0,0 +1,25 @@ +common: + filter: not CONFIG_NATIVE_LIBC + tags: + - posix + - posix_system_database + # 1 tier0 platform per supported architecture + platform_key: + - arch + - simulation + min_flash: 64 + min_ram: 32 +tests: + portability.posix.posix_system_database: {} + portability.posix.posix_system_database.minimal: + extra_configs: + - CONFIG_MINIMAL_LIBC=y + portability.posix.posix_system_database.newlib: + filter: TOOLCHAIN_HAS_NEWLIB == 1 + extra_configs: + - CONFIG_NEWLIB_LIBC=y + portability.posix.posix_system_database.picolibc: + tags: picolibc + filter: CONFIG_PICOLIBC_SUPPORTED + extra_configs: + - CONFIG_PICOLIBC=y From e352017093ffc53d74168f70e1ebc6d0702f66ea Mon Sep 17 00:00:00 2001 From: Chris Friedt Date: Sun, 5 Jan 2025 22:03:21 -0500 Subject: [PATCH 08/13] doc: posix: options: mark POSIX_SYSTEM_DATABASE as supported Mark the POSIX_SYSTEM_DATABASE Option Group as supported. Signed-off-by: Chris Friedt --- .../portability/posix/option_groups/index.rst | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/doc/services/portability/posix/option_groups/index.rst b/doc/services/portability/posix/option_groups/index.rst index 97d124372c4a..9f4ece60abba 100644 --- a/doc/services/portability/posix/option_groups/index.rst +++ b/doc/services/portability/posix/option_groups/index.rst @@ -574,6 +574,22 @@ Enable this option group with :kconfig:option:`CONFIG_POSIX_SYSTEM_DATABASE_R`. getpwnam_r(),yes getpwuid_r(),yes +.. _posix_option_group_system_database: + +POSIX_SYSTEM_DATABASE ++++++++++++++++++++++ + +Enable this option group with :kconfig:option:`CONFIG_POSIX_SYSTEM_DATABASE`. + +.. csv-table:: POSIX_SYSTEM_DATABASE + :header: API, Supported + :widths: 50,10 + + getgrgid(),yes + getgrnam(),yes + getpwnam(),yes + getpwuid(),yes + .. _posix_option_group_threads_base: POSIX_THREADS_BASE From 17739f88a1ae3911bf518bba840a0d93bf619eb5 Mon Sep 17 00:00:00 2001 From: Chris Friedt Date: Tue, 24 Dec 2024 12:42:58 -0500 Subject: [PATCH 09/13] posix: options: implement the POSIX_FILE_LOCKING Option Group Provide an implementation of the POSIX_FILE_LOCKING Option Group. Signed-off-by: Chris Friedt --- lib/libc/Kconfig | 1 + lib/os/fdtable.c | 107 ++++++++++++++++++++++--- lib/posix/options/CMakeLists.txt | 6 ++ lib/posix/options/Kconfig | 1 + lib/posix/options/Kconfig.file_locking | 14 ++++ lib/posix/options/file_locking.c | 69 ++++++++++++++++ 6 files changed, 186 insertions(+), 12 deletions(-) create mode 100644 lib/posix/options/Kconfig.file_locking create mode 100644 lib/posix/options/file_locking.c diff --git a/lib/libc/Kconfig b/lib/libc/Kconfig index 5d981e4719b0..98f3f4c24d73 100644 --- a/lib/libc/Kconfig +++ b/lib/libc/Kconfig @@ -94,6 +94,7 @@ config PICOLIBC select LIBC_ERRNO if THREAD_LOCAL_STORAGE select NEED_LIBC_MEM_PARTITION select TC_PROVIDES_POSIX_C_LANG_SUPPORT_R + select TC_PROVIDES_POSIX_FILE_LOCKING imply COMMON_LIBC_MALLOC imply COMMON_LIBC_ABORT depends on PICOLIBC_SUPPORTED diff --git a/lib/os/fdtable.c b/lib/os/fdtable.c index 013fb4b07cb9..0018f9d87283 100644 --- a/lib/os/fdtable.c +++ b/lib/os/fdtable.c @@ -339,18 +339,13 @@ static bool supports_pread_pwrite(uint32_t mode) } } -static ssize_t zvfs_rw(int fd, void *buf, size_t sz, bool is_write, const size_t *from_offset) +static ssize_t zvfs_rw_unlocked(int fd, void *buf, size_t sz, bool is_write, + const size_t *from_offset) { bool prw; ssize_t res; const size_t *off; - if (_check_fd(fd) < 0) { - return -1; - } - - (void)k_mutex_lock(&fdtable[fd].lock, K_FOREVER); - prw = supports_pread_pwrite(fdtable[fd].mode); if (from_offset != NULL && !prw) { /* @@ -358,8 +353,7 @@ static ssize_t zvfs_rw(int fd, void *buf, size_t sz, bool is_write, const size_t * Otherwise, it's a bug. */ errno = ENOTSUP; - res = -1; - goto unlock; + return -1; } /* If there is no specified from_offset, then use the current offset of the fd */ @@ -367,15 +361,15 @@ static ssize_t zvfs_rw(int fd, void *buf, size_t sz, bool is_write, const size_t if (is_write) { if (fdtable[fd].vtable->write_offs == NULL) { - res = -1; errno = EIO; + return -1; } else { res = fdtable[fd].vtable->write_offs(fdtable[fd].obj, buf, sz, *off); } } else { if (fdtable[fd].vtable->read_offs == NULL) { - res = -1; errno = EIO; + return -1; } else { res = fdtable[fd].vtable->read_offs(fdtable[fd].obj, buf, sz, *off); } @@ -388,7 +382,21 @@ static ssize_t zvfs_rw(int fd, void *buf, size_t sz, bool is_write, const size_t fdtable[fd].offset += res; } -unlock: + return res; +} + +static ssize_t zvfs_rw(int fd, void *buf, size_t sz, bool is_write, const size_t *from_offset) +{ + ssize_t res; + + if (_check_fd(fd) < 0) { + return -1; + } + + (void)k_mutex_lock(&fdtable[fd].lock, K_FOREVER); + + res = zvfs_rw_unlocked(fd, buf, sz, is_write, from_offset); + k_mutex_unlock(&fdtable[fd].lock); return res; @@ -562,6 +570,81 @@ int zvfs_ioctl(int fd, unsigned long request, va_list args) return fdtable[fd].vtable->ioctl(fdtable[fd].obj, request, args); } +int zvfs_lock_file(FILE *file, k_timeout_t timeout) +{ + int fd; + int prev_errno; + struct fd_entry *entry; + + fd = z_libc_file_get_fd(file); + prev_errno = errno; + if (_check_fd(fd) < 0) { + if (errno != prev_errno) { + errno = prev_errno; + } + return -1; + } + + entry = &fdtable[fd]; + return k_mutex_lock(&entry->lock, timeout); +} + +int zvfs_unlock_file(FILE *file) +{ + int fd; + int prev_errno; + struct fd_entry *entry; + + fd = z_libc_file_get_fd(file); + prev_errno = errno; + if (_check_fd(fd) < 0) { + if (errno != prev_errno) { + errno = prev_errno; + } + return -1; + } + + entry = &fdtable[fd]; + return k_mutex_unlock(&entry->lock); +} + +int zvfs_getc_unlocked(FILE *stream) +{ + int fd; + int res; + char buf; + + fd = z_libc_file_get_fd(stream); + if (_check_fd(fd) < 0) { + return EOF; + } + + res = zvfs_rw_unlocked(fd, &buf, 1, false, NULL); + if (res <= 0) { + return EOF; + } + + return (int)buf; +} + +int zvfs_putc_unlocked(int c, FILE *stream) +{ + int fd; + int res; + char buf = (char)c; + + fd = z_libc_file_get_fd(stream); + if (_check_fd(fd) < 0) { + return EOF; + } + + res = zvfs_rw_unlocked(fd, &buf, 1, true, NULL); + if (res <= 0) { + return EOF; + } + + return c; +} #if defined(CONFIG_POSIX_DEVICE_IO) /* diff --git a/lib/posix/options/CMakeLists.txt b/lib/posix/options/CMakeLists.txt index 2a663651fcc3..8ec66015addc 100644 --- a/lib/posix/options/CMakeLists.txt +++ b/lib/posix/options/CMakeLists.txt @@ -76,6 +76,12 @@ if (NOT CONFIG_TC_PROVIDES_POSIX_FD_MGMT) ) endif() +if (NOT CONFIG_TC_PROVIDES_POSIX_FILE_LOCKING) + zephyr_library_sources_ifdef(CONFIG_POSIX_FILE_LOCKING + file_locking.c + ) +endif() + if (NOT CONFIG_TC_PROVIDES_POSIX_FILE_SYSTEM) zephyr_library_sources_ifdef(CONFIG_POSIX_FILE_SYSTEM fs.c) endif() diff --git a/lib/posix/options/Kconfig b/lib/posix/options/Kconfig index f526d9bce73b..b036fa70fef8 100644 --- a/lib/posix/options/Kconfig +++ b/lib/posix/options/Kconfig @@ -14,6 +14,7 @@ rsource "Kconfig.c_lang_r" rsource "Kconfig.c_lib_ext" rsource "Kconfig.device_io" rsource "Kconfig.fd_mgmt" +rsource "Kconfig.file_locking" rsource "Kconfig.file_system_r" rsource "Kconfig.fs" rsource "Kconfig.mem" diff --git a/lib/posix/options/Kconfig.file_locking b/lib/posix/options/Kconfig.file_locking new file mode 100644 index 000000000000..69ea46b27975 --- /dev/null +++ b/lib/posix/options/Kconfig.file_locking @@ -0,0 +1,14 @@ +# Copyright (c) 2024 Tenstorrent AI ULC +# +# SPDX-License-Identifier: Apache-2.0 + +config POSIX_FILE_LOCKING + bool "POSIX file locking" + help + Select 'y' here and the following functions will be available: + + flockfile(), ftrylockfile(), funlockfile(), getc_unlocked(), getchar_unlocked(), + putc_unlocked(), putchar_unlocked(). + + For more information, please see + https://pubs.opengroup.org/onlinepubs/9699919799/xrat/V4_subprofiles.html diff --git a/lib/posix/options/file_locking.c b/lib/posix/options/file_locking.c new file mode 100644 index 000000000000..f01af1dd5d5d --- /dev/null +++ b/lib/posix/options/file_locking.c @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2024 Tenstorrent AI ULC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include +#include + +int zvfs_lock_file(FILE *file, k_timeout_t timeout); +int zvfs_unlock_file(FILE *file); +int zvfs_getc_unlocked(FILE *stream); +int zvfs_putc_unlocked(int c, FILE *stream); + +/* + * This is incorrectly declared by (at least) newlib to be a macro with 2 arguments + * but it only takes 1 argument. + * + * Undefine any possible macro before attempting to define a duplicately-named function. + */ +#undef putchar_unlocked + +/* + * This is incorrectly declared by (at least) newlib to be a macro with 0 arguments + * but it should take 1 argument. + * + * Undefine any possible macro before attempting to define a duplicately-named function. + */ +#undef getchar_unlocked + +void flockfile(FILE *file) +{ + while (zvfs_lock_file(file, K_FOREVER) != 0) { + k_yield(); + } +} + +int ftrylockfile(FILE *file) +{ + return zvfs_lock_file(file, K_NO_WAIT); +} + +void funlockfile(FILE *file) +{ + (void)zvfs_unlock_file(file); +} + +int getc_unlocked(FILE *stream) +{ + return zvfs_getc_unlocked(stream); +} + +int getchar_unlocked(void) +{ + return zvfs_getc_unlocked(stdin); +} + +int putc_unlocked(int c, FILE *stream) +{ + return zvfs_putc_unlocked(c, stream); +} + +int putchar_unlocked(int c) +{ + return zvfs_putc_unlocked(c, stdout); +} From 9cbb9f48421207f4321057b4aabdff335a18c1ec Mon Sep 17 00:00:00 2001 From: Chris Friedt Date: Mon, 30 Dec 2024 19:59:23 -0500 Subject: [PATCH 10/13] tests: posix: add testsuite for POSIX_FILE_LOCKING Option Group Add a testsuite for the POSIX_FILE_LOCKING Option Group. Signed-off-by: Chris Friedt --- tests/posix/file_locking/CMakeLists.txt | 10 + tests/posix/file_locking/Kconfig | 21 ++ tests/posix/file_locking/app.overlay | 14 + tests/posix/file_locking/prj.conf | 13 + tests/posix/file_locking/src/fs.c | 93 +++++++ tests/posix/file_locking/src/main.c | 356 ++++++++++++++++++++++++ tests/posix/file_locking/testcase.yaml | 25 ++ 7 files changed, 532 insertions(+) create mode 100644 tests/posix/file_locking/CMakeLists.txt create mode 100644 tests/posix/file_locking/Kconfig create mode 100644 tests/posix/file_locking/app.overlay create mode 100644 tests/posix/file_locking/prj.conf create mode 100644 tests/posix/file_locking/src/fs.c create mode 100644 tests/posix/file_locking/src/main.c create mode 100644 tests/posix/file_locking/testcase.yaml diff --git a/tests/posix/file_locking/CMakeLists.txt b/tests/posix/file_locking/CMakeLists.txt new file mode 100644 index 000000000000..c70ae57c7845 --- /dev/null +++ b/tests/posix/file_locking/CMakeLists.txt @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(posix_file_locking) + +FILE(GLOB app_sources src/*.c) +target_sources(app PRIVATE ${app_sources}) + +target_compile_options(app PRIVATE -U_POSIX_C_SOURCE -D_POSIX_C_SOURCE=200809L) diff --git a/tests/posix/file_locking/Kconfig b/tests/posix/file_locking/Kconfig new file mode 100644 index 000000000000..2e1d89dacd24 --- /dev/null +++ b/tests/posix/file_locking/Kconfig @@ -0,0 +1,21 @@ +# Copyright (c) 2024 Tenstorrent AI ULC +# SPDX-License-Identifier: Apache-2.0 + +source "Kconfig.zephyr" + +menu "File Locking Tests" + +config TEST_NEGLIGIBLE_DELAY_MS + int "Negligible delay in milliseconds" + default 10 + help + Delays less than or equal to this value are considered neglible for the purposes of this + testsuite. + +config TEST_LOCK_PERIOD_MS + int "Time to hold a lock in milliseconds" + default 100 + help + The amount of time to hold a lock before releasing it, for the purposes of this testuite. + +endmenu diff --git a/tests/posix/file_locking/app.overlay b/tests/posix/file_locking/app.overlay new file mode 100644 index 000000000000..87ae21b1e647 --- /dev/null +++ b/tests/posix/file_locking/app.overlay @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/ { + ramdisk0 { + compatible = "zephyr,ram-disk"; + disk-name = "RAM"; + sector-size = <512>; + sector-count = <160>; + }; +}; diff --git a/tests/posix/file_locking/prj.conf b/tests/posix/file_locking/prj.conf new file mode 100644 index 000000000000..c98a51853401 --- /dev/null +++ b/tests/posix/file_locking/prj.conf @@ -0,0 +1,13 @@ +CONFIG_POSIX_API=y +CONFIG_ZTEST=y + +CONFIG_POSIX_AEP_CHOICE_BASE=y +CONFIG_POSIX_FILE_LOCKING=y +CONFIG_FAT_FILESYSTEM_ELM=y + +# pthreads are used for simplicity here, although that isn't strictly necessary +CONFIG_THREAD_STACK_INFO=y +CONFIG_DYNAMIC_THREAD=y +CONFIG_DYNAMIC_THREAD_POOL_SIZE=4 + +CONFIG_MAIN_STACK_SIZE=4096 diff --git a/tests/posix/file_locking/src/fs.c b/tests/posix/file_locking/src/fs.c new file mode 100644 index 000000000000..ed53e424ddab --- /dev/null +++ b/tests/posix/file_locking/src/fs.c @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2024 Tenstorrent AI ULC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#include +#include +#include +#include + +static FATFS _fs; + +static struct fs_mount_t _mnt = { + .type = FS_FATFS, + .mnt_point = "/", + .fs_data = &_fs, +}; + +struct _entry { + const char *const name; + const char *const data; + FILE *fp; +}; + +static struct _entry _data[] = { + {.name = "/tmp/foo.txt", .data = ""}, + {.name = "/tmp/bar.txt", .data = ""}, +}; + +void setup_callback(void *arg); +void before_callback(void *arg); +void after_callback(void *arg); + +void *setup(void) +{ + int ret; + + memset(&_fs, 0, sizeof(_fs)); + + ret = fs_mount(&_mnt); + zassert_ok(ret, "mount failed: %d", ret); + + ret = fs_mkdir("/tmp"); + zassert_ok(ret, "mkdir failed: %d", ret); + + setup_callback(NULL); + + return NULL; +} + +void before(void *arg) +{ + ARG_UNUSED(arg); + + ARRAY_FOR_EACH_PTR(_data, entry) { + int len; + int ret; + + entry->fp = fopen(entry->name, "w+"); + zassert_not_null(entry->fp, "fopen() failed: %d", errno); + + len = strlen(entry->data); + if (len > 0) { + ret = (int)fwrite(entry->data, len, 1, entry->fp); + zassert_equal(ret, len, "%s returned %d instead of %d: %d", "fwrite", ret, + len, errno); + } + + before_callback(entry->fp); + }; +} + +void after(void *arg) +{ + ARG_UNUSED(arg); + + ARRAY_FOR_EACH_PTR(_data, entry) { + (void)fclose(entry->fp); + }; + + after_callback(NULL); +} + +void teardown(void *arg) +{ + ARG_UNUSED(arg); + (void)fs_unmount(&_mnt); +} diff --git a/tests/posix/file_locking/src/main.c b/tests/posix/file_locking/src/main.c new file mode 100644 index 000000000000..a6d3583f29f9 --- /dev/null +++ b/tests/posix/file_locking/src/main.c @@ -0,0 +1,356 @@ +/* + * Copyright (c) 2024 Tenstorrent AI ULC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#include +#include + +/* Picolibc has a conflicting macro definition with the wrong number of arguments */ +#undef putchar_unlocked +int putchar_unlocked(int c); + +#define N 2 + +struct thread_ctx { + int delay_ms; + int hold_ms; + int lock_count; + FILE *fp; + bool retry; + bool success; +}; + +static size_t fp_idx; +static FILE *fp[N]; +static struct thread_ctx ctx[N]; + +/* + * > The functions shall behave as if there is a lock count associated with each (FILE *) + * > object. This count is implicitly initialized to zero when the (FILE *) object is + * > created. + * + * 1. A newly opened FILE object will have negligible delay associated with locking it. + * + * > When the count is positive, a single thread owns the (FILE *) object + * + * 1. Two threads can call flockfile() on separate files with neglible delay. + * 2. When two threads call flockfile() simultaneously on the same file, the second thread + * to acquire the lock will experience a delay equal to or greater than the amount of + * time that the first thread holds the lock. + * + * > Each call to funlockfile() shall decrement the count + * + * 1. If a thread owns the lock associated with a file, that thread can call funlockfile() + * with negligible delay. + * 2. If a thread has called flockfile() multiple times for the same file, that thread must + * call funlockfile() the same number of times to release the lock. + * + * > The behavior is undefined if a thread other than the current owner calls the + * > funlockfile() function. + * + * 1. Likely what we'll do in this situation is have a Kconfig option to verify the caller + * of funlockfile(). If the option is enabled, we will assert. If the option is not + * enabled, enter an infinite loop. + */ + +static void *flockfile_thread_entry(void *arg) +{ + struct thread_ctx *const ctx = (struct thread_ctx *)arg; + + if (ctx->delay_ms > 0) { + zassert_ok(k_msleep(ctx->delay_ms)); + } + + for (int i = 0; i < ctx->lock_count; ++i) { + flockfile(ctx->fp); + if (ctx->hold_ms > 0) { + k_sleep(K_MSEC(ctx->hold_ms)); + } + funlockfile(ctx->fp); + } + + return NULL; +} + +static int flockfile_retry(FILE *fp, bool retry) +{ + while (true) { + int ret = ftrylockfile(fp); + + if (!retry) { + return ret; + } + if (ret == 0) { + return 0; + } + k_yield(); + } + + CODE_UNREACHABLE; +} + +static void *ftrylockfile_thread_entry(void *arg) +{ + struct thread_ctx *const ctx = (struct thread_ctx *)arg; + + zassert_ok(k_msleep(ctx->delay_ms)); + + for (int i = 0; i < ctx->lock_count; ++i) { + int ret = flockfile_retry(ctx->fp, ctx->retry); + + if (ret == 0) { + ctx->success = true; + } + + if (ctx->hold_ms > 0) { + k_sleep(K_MSEC(ctx->hold_ms)); + } + funlockfile(ctx->fp); + } + + return NULL; +} + +static void flockfile_common(bool try) +{ + int64_t now, then; + pthread_t th[2]; + void *(*thread_entry)(void *) = try ? ftrylockfile_thread_entry : flockfile_thread_entry; + + if (false) { + /* degenerate cases */ + flockfile_retry(NULL, try); /* this will throw an assertion if enabled */ + } + + /* + * > The functions shall behave as if there is a lock count associated with each + * (FILE *) > object. This count is implicitly initialized to zero when the (FILE *) + * object is > created. + * + * 1. A newly opened FILE object will have negligible delay associated with locking + * it, because they are not waiting for another thread to release the lock. + * + * > When the count is positive, a single thread owns the (FILE *) object + * + * 1. Two threads can call flockfile() on separate files with neglible delay. + * 2. When two threads call flockfile() simultaneously on the same file, the second + * thread to acquire the lock will experience a delay equal to or greater than the + * amount of time that the first thread holds the lock. + */ + + /* check for locking in parallel - should be no contention */ + for (int i = 0; i < N; ++i) { + ctx[i] = (struct thread_ctx){ + .lock_count = 1, + .hold_ms = 0, + .fp = fp[i], + .retry = try, + }; + } + + then = k_uptime_get(); + for (int i = 0; i < N; ++i) { + zassert_ok(pthread_create(&th[i], NULL, thread_entry, &ctx[i])); + } + k_yield(); + ARRAY_FOR_EACH_PTR(th, it) { + zassert_ok(pthread_join(*it, NULL)); + } + now = k_uptime_get(); + zexpect_true(now - then <= CONFIG_TEST_NEGLIGIBLE_DELAY_MS, "delay of %d ms exceeds %d ms", + now - then, CONFIG_TEST_NEGLIGIBLE_DELAY_MS); + + /* + * Check for locking the same file once - should be contention resulting in a delay + * of twice CONFIG_TEST_LOCK_PERIOD_MS. + */ + ARRAY_FOR_EACH_PTR(ctx, it) { + *it = (struct thread_ctx){ + .lock_count = 1, + .hold_ms = CONFIG_TEST_LOCK_PERIOD_MS, + .fp = fp[0], + .retry = try, + }; + } + + then = k_uptime_get(); + ARRAY_FOR_EACH(th, i) { + zassert_ok(pthread_create(&th[i], NULL, thread_entry, &ctx[i])); + } + k_yield(); + ARRAY_FOR_EACH_PTR(th, it) { + zassert_ok(pthread_join(*it, NULL)); + } + now = k_uptime_get(); + zexpect_true(now - then >= 2 * CONFIG_TEST_LOCK_PERIOD_MS, "delay of %d ms less than %d ms", + now - then, 2 * CONFIG_TEST_LOCK_PERIOD_MS); + + /* + * Check for locking the same file twice - should be contention resulting in a delay + * of four times CONFIG_TEST_LOCK_PERIOD_MS. + */ + ARRAY_FOR_EACH_PTR(ctx, it) { + *it = (struct thread_ctx){ + .lock_count = 2, + .hold_ms = CONFIG_TEST_LOCK_PERIOD_MS, + .fp = fp[0], + .retry = try, + }; + } + + then = k_uptime_get(); + ARRAY_FOR_EACH(ctx, i) { + zassert_ok(pthread_create(&th[i], NULL, thread_entry, &ctx[i])); + } + k_yield(); + ARRAY_FOR_EACH_PTR(th, it) { + zassert_ok(pthread_join(*it, NULL)); + } + now = k_uptime_get(); + zexpect_true(now - then >= 4 * CONFIG_TEST_LOCK_PERIOD_MS, "delay of %d ms less than %d ms", + now - then, 4 * CONFIG_TEST_LOCK_PERIOD_MS); +} + +ZTEST(posix_file_locking, test_flockfile) +{ + flockfile_common(false); +} + +ZTEST(posix_file_locking, test_ftrylockfile) +{ + int64_t now, then; + pthread_t th[2]; + + flockfile_common(true); + + /* additional, special cases for ftrylockfile() */ + ARRAY_FOR_EACH_PTR(ctx, it) { + *it = (struct thread_ctx){ + .lock_count = 1, + .hold_ms = CONFIG_TEST_LOCK_PERIOD_MS, + .fp = fp[0], + .retry = false, + }; + } + + then = k_uptime_get(); + ARRAY_FOR_EACH(ctx, i) { + zassert_ok(pthread_create(&th[i], NULL, ftrylockfile_thread_entry, &ctx[i])); + } + k_yield(); + ARRAY_FOR_EACH_PTR(th, it) { + zassert_ok(pthread_join(*it, NULL)); + } + now = k_uptime_get(); + zexpect_true(now - then >= CONFIG_TEST_LOCK_PERIOD_MS, "delay of %d ms less than %d ms", + now - then, CONFIG_TEST_LOCK_PERIOD_MS); + + int success = 0; + int fail = 0; + + ARRAY_FOR_EACH_PTR(ctx, it) { + success += it->success; + fail += !it->success; + } + zexpect_true(N > 1); + if (IS_ENABLED(CONFIG_PICOLIBC)) { + if (success != 1) { + TC_PRINT("Note: successes equal to %d\n", success); + } + if (fail < 1) { + TC_PRINT("Note: failures equal %d\n", fail); + } + } else { + zexpect_equal(success, 1); + zexpect_true(fail >= 1); + } +} + +ZTEST(posix_file_locking, test_funlockfile) +{ + /* this is already excercised in test_flockfile_common(), so just a simple check here */ + zexpect_ok(ftrylockfile(fp[0])); + funlockfile(fp[0]); +} + +ZTEST(posix_file_locking, test_getc_unlocked) +{ + flockfile(fp[0]); + zassert_equal(EOF, getc_unlocked(fp[0])); + funlockfile(fp[0]); + + static const char msg[] = "Hello"; + int expect = strlen(msg); + int actual = fwrite(msg, 1, expect, fp[0]); + + zassert_equal(actual, expect, "wrote %d bytes, expected %d", actual, expect); + rewind(fp[0]); + + flockfile(fp[0]); + ARRAY_FOR_EACH(msg, i) { + if (msg[i] == '\0') { + break; + } + + int actual = getc_unlocked(fp[0]); + + zassert_equal((int)msg[i], actual, "expected %c, got %c", msg[i], actual); + } + funlockfile(fp[0]); +} + +ZTEST(posix_file_locking, test_getchar_unlocked) +{ + flockfile(stdin); + zassert_equal(EOF, getchar_unlocked()); + funlockfile(stdin); +} + +ZTEST(posix_file_locking, test_putc_unlocked) +{ + flockfile(fp[0]); + zassert_equal('*', putc_unlocked('*', fp[0])); + funlockfile(fp[0]); +} + +ZTEST(posix_file_locking, test_putchar_unlocked) +{ + flockfile(stdout); + zassert_equal('*', putchar_unlocked('*')); + funlockfile(stdout); +} + +void *setup(void); +void before(void *arg); +void after(void *arg); +void teardown(void *arg); + +void setup_callback(void *arg) +{ + ARG_UNUSED(arg); + + fp_idx = 0; +} + +void before_callback(void *arg) +{ + FILE *file = arg; + + zassert_true(fp_idx < ARRAY_SIZE(fp), "fps[] overflow"); + fp[fp_idx++] = file; +} + +void after_callback(void *arg) +{ + ARG_UNUSED(arg); + + fp_idx = 0; +} + +ZTEST_SUITE(posix_file_locking, NULL, setup, before, after, teardown); diff --git a/tests/posix/file_locking/testcase.yaml b/tests/posix/file_locking/testcase.yaml new file mode 100644 index 000000000000..07da2348639a --- /dev/null +++ b/tests/posix/file_locking/testcase.yaml @@ -0,0 +1,25 @@ +common: + filter: not CONFIG_NATIVE_LIBC + tags: + - posix + - posix_file_locking + # 1 tier0 platform per supported architecture + platform_key: + - arch + - simulation + min_flash: 64 + min_ram: 32 +tests: + portability.posix.posix_file_locking: {} + portability.posix.posix_file_locking.minimal: + extra_configs: + - CONFIG_MINIMAL_LIBC=y + portability.posix.posix_file_locking.newlib: + filter: TOOLCHAIN_HAS_NEWLIB == 1 + extra_configs: + - CONFIG_NEWLIB_LIBC=y + portability.posix.posix_file_locking.picolibc: + tags: picolibc + filter: CONFIG_PICOLIBC_SUPPORTED + extra_configs: + - CONFIG_PICOLIBC=y From 611a38d78b234a7ff1ee62fa3ab629f7e65d9415 Mon Sep 17 00:00:00 2001 From: Chris Friedt Date: Sun, 5 Jan 2025 22:23:51 -0500 Subject: [PATCH 11/13] doc: posix: options: mark POSIX_FILE_LOCKING as suppported Mark POSIX_FILE_LOCKING as supported. This Option Group is part of the _POSIX_THREAD_SAFE_FUNCTIONS Option and is mandatory for all conforming implementations. Signed-off-by: Chris Friedt --- .../portability/posix/option_groups/index.rst | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/doc/services/portability/posix/option_groups/index.rst b/doc/services/portability/posix/option_groups/index.rst index 9f4ece60abba..1d0ee7cdde1a 100644 --- a/doc/services/portability/posix/option_groups/index.rst +++ b/doc/services/portability/posix/option_groups/index.rst @@ -227,17 +227,19 @@ Enable this option group with :kconfig:option:`CONFIG_POSIX_FD_MGMT`. POSIX_FILE_LOCKING ++++++++++++++++++ +Enable this option group with :kconfig:option:`CONFIG_POSIX_FILE_LOCKING`. + .. csv-table:: POSIX_FILE_LOCKING :header: API, Supported :widths: 50,10 - flockfile(), - ftrylockfile(), - funlockfile(), - getc_unlocked(), - getchar_unlocked(), - putc_unlocked(), - putchar_unlocked(), + flockfile(), yes + ftrylockfile(), yes + funlockfile(), yes + getc_unlocked(), yes + getchar_unlocked(), yes + putc_unlocked(), yes + putchar_unlocked(), yes .. _posix_option_group_file_system: From a5f0778b40afe4fc437e859f486b7fcdc8c6896e Mon Sep 17 00:00:00 2001 From: Chris Friedt Date: Tue, 24 Dec 2024 12:49:22 -0500 Subject: [PATCH 12/13] posix: options: define threadsafe fns in terms of option groups The functions associated with the _POSIX_THREAD_SAFE_FUNCTIONS Option are members of other POSIX Option Groups. Specifically, POSIX_C_LANG_SUPPORT_R, POSIX_FILE_LOCKING, POSIX_FILE_SYSTEM_R, and POSIX_SYSTEM_DATABASE_R. Moreover, _POSIX_THREAD_SAFE_FUNCTIONS is required to be available with any conforming implementation. Therefore, it does not make sense to have a Kconfig option to turn it off or on; it should be enabled or disabled based on whether the required POSIX Option Groups are active. Signed-off-by: Chris Friedt --- include/zephyr/posix/posix_features.h | 3 ++- lib/posix/options/Kconfig.pthread | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/include/zephyr/posix/posix_features.h b/include/zephyr/posix/posix_features.h index cb804fd61ca4..513529bc486d 100644 --- a/include/zephyr/posix/posix_features.h +++ b/include/zephyr/posix/posix_features.h @@ -161,7 +161,8 @@ /* #define _POSIX_THREAD_ROBUST_PRIO_INHERIT (-1L) */ /* #define _POSIX_THREAD_ROBUST_PRIO_PROTECT (-1L) */ -#ifdef CONFIG_POSIX_THREAD_SAFE_FUNCTIONS +#if defined(CONFIG_POSIX_C_LANG_SUPPORT_R) && defined(CONFIG_POSIX_FILE_LOCKING) && \ + defined(CONFIG_POSIX_FILE_SYSTEM_R) && defined(CONFIG_POSIX_SYSTEM_DATABASE_R) #define _POSIX_THREAD_SAFE_FUNCTIONS _POSIX_VERSION #endif diff --git a/lib/posix/options/Kconfig.pthread b/lib/posix/options/Kconfig.pthread index 438506fe2182..25d632a955ea 100644 --- a/lib/posix/options/Kconfig.pthread +++ b/lib/posix/options/Kconfig.pthread @@ -159,8 +159,11 @@ config POSIX_THREAD_PRIO_PROTECT config POSIX_THREAD_SAFE_FUNCTIONS bool "POSIX thread-safe functions" - select POSIX_FILE_SYSTEM_R select POSIX_C_LANG_SUPPORT_R + select POSIX_FILE_LOCKING + select POSIX_FILE_SYSTEM_R + select POSIX_SYSTEM_DATABASE_R + select POSIX_SYNCHRONIZED_IO help Select 'y' here to enable POSIX thread-safe functions including asctime_r(), ctime_r(), flockfile(), ftrylockfile(), funlockfile(), getc_unlocked(), getchar_unlocked(), From 694c739e8540507611a137d55916ab888fd37cc0 Mon Sep 17 00:00:00 2001 From: Chris Friedt Date: Sun, 5 Jan 2025 22:11:34 -0500 Subject: [PATCH 13/13] doc: posix: options: mark _POSIX_THREAD_SAFE_FUNCTIONS supported Mark the _POSIX_THREAD_SAFE_FUNCTIONS Option as supported. This Option is required mandatory for any conformant implementation and is comprised of functions belonging to the following Option Groups: - POSIX_C_LANG_SUPPORT_R - POSIX_FILE_LOCKING - POSIX_FILE_SYSTEM_R - POSIX_SYSTEM_DATABASE_R Signed-off-by: Chris Friedt --- .../portability/posix/option_groups/index.rst | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/doc/services/portability/posix/option_groups/index.rst b/doc/services/portability/posix/option_groups/index.rst index 1d0ee7cdde1a..5b61f37f0755 100644 --- a/doc/services/portability/posix/option_groups/index.rst +++ b/doc/services/portability/posix/option_groups/index.rst @@ -1087,7 +1087,9 @@ Enable this option with :kconfig:option:`CONFIG_POSIX_THREAD_PRIORITY_SCHEDULING _POSIX_THREAD_SAFE_FUNCTIONS ++++++++++++++++++++++++++++ -Enable this option with :kconfig:option:`CONFIG_POSIX_THREAD_SAFE_FUNCTIONS`. +Enable this option with :kconfig:option:`CONFIG_POSIX_C_LANG_SUPPORT_R`, +:kconfig:option:`CONFIG_POSIX_FILE_LOCKING`, :kconfig:option:`CONFIG_POSIX_FILE_SYSTEM_R`, +and :kconfig:option:`CONFIG_POSIX_SYSTEM_DATABASE_R`. .. csv-table:: _POSIX_THREAD_SAFE_FUNCTIONS :header: API, Supported @@ -1095,19 +1097,19 @@ Enable this option with :kconfig:option:`CONFIG_POSIX_THREAD_SAFE_FUNCTIONS`. asctime_r(), yes ctime_r(), yes (UTC timezone only) - flockfile(), - ftrylockfile(), - funlockfile(), - getc_unlocked(), - getchar_unlocked(), - getgrgid_r(),yes :ref:`†` - getgrnam_r(),yes :ref:`†` - getpwnam_r(),yes :ref:`†` - getpwuid_r(),yes :ref:`†` + flockfile(), yes + ftrylockfile(), yes + funlockfile(), yes + getc_unlocked(), yes + getchar_unlocked(), yes + getgrgid_r(),yes + getgrnam_r(),yes + getpwnam_r(),yes + getpwuid_r(),yes gmtime_r(), yes localtime_r(), yes (UTC timezone only) - putc_unlocked(), - putchar_unlocked(), + putc_unlocked(), yes + putchar_unlocked(), yes rand_r(), yes readdir_r(), yes strerror_r(), yes