From 64e8d347584b5172c7b810eedc3f7a08e38981a0 Mon Sep 17 00:00:00 2001 From: Chris Friedt Date: Tue, 24 Dec 2024 12:42:58 -0500 Subject: [PATCH 1/3] 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 150ab256c6ff..bcfc8c867530 100644 --- a/lib/libc/Kconfig +++ b/lib/libc/Kconfig @@ -86,6 +86,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 depends on PICOLIBC_SUPPORTED help diff --git a/lib/os/fdtable.c b/lib/os/fdtable.c index 0299e89812a3..95e140e45fec 100644 --- a/lib/os/fdtable.c +++ b/lib/os/fdtable.c @@ -319,18 +319,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) { /* @@ -338,8 +333,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 */ @@ -347,15 +341,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); } @@ -368,7 +362,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; @@ -535,6 +543,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 b6e9d0bad4e2..07518b3ee8f2 100644 --- a/lib/posix/options/CMakeLists.txt +++ b/lib/posix/options/CMakeLists.txt @@ -67,6 +67,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 c7e60d9e3238..cbeff514899c 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 514268c180bded36090b173344480b428b320640 Mon Sep 17 00:00:00 2001 From: Chris Friedt Date: Mon, 30 Dec 2024 19:59:23 -0500 Subject: [PATCH 2/3] 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 b5ae1d13982608d2c0b3cdf8deb51fa4abed2969 Mon Sep 17 00:00:00 2001 From: Chris Friedt Date: Sun, 5 Jan 2025 22:23:51 -0500 Subject: [PATCH 3/3] 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 87a9d50caab9..222485ea4932 100644 --- a/doc/services/portability/posix/option_groups/index.rst +++ b/doc/services/portability/posix/option_groups/index.rst @@ -208,17 +208,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: