From 87729baabee1f477bdc15555c99559374a98f9d5 Mon Sep 17 00:00:00 2001 From: Yong Cong Sin Date: Thu, 13 Jun 2024 18:43:28 +0800 Subject: [PATCH] posix: implement file locking functions Implement file locking functions and added testcase. Signed-off-by: Yong Cong Sin --- .../portability/posix/option_groups/index.rst | 12 +- lib/libc/Kconfig | 1 + lib/libc/minimal/include/stdio.h | 6 + lib/os/fdtable.c | 29 +++++ lib/posix/options/CMakeLists.txt | 1 + lib/posix/options/Kconfig | 1 + lib/posix/options/Kconfig.file_locking | 38 ++++++ lib/posix/options/Kconfig.pthread | 1 + lib/posix/options/file_locking.c | 37 ++++++ tests/posix/common/src/file_locking.c | 116 ++++++++++++++++++ 10 files changed, 236 insertions(+), 6 deletions(-) create mode 100644 lib/posix/options/Kconfig.file_locking create mode 100644 lib/posix/options/file_locking.c create mode 100644 tests/posix/common/src/file_locking.c diff --git a/doc/services/portability/posix/option_groups/index.rst b/doc/services/portability/posix/option_groups/index.rst index e67523ae3d7c5c..47127adb042d88 100644 --- a/doc/services/portability/posix/option_groups/index.rst +++ b/doc/services/portability/posix/option_groups/index.rst @@ -564,9 +564,9 @@ This table lists service support status in Zephyr for `POSIX_FD_MGMT`: :header: API, Supported :widths: 50,10 - flockfile(), - ftrylockfile(), - funlockfile(), + flockfile(), yes + ftrylockfile(), yes + funlockfile(), yes getc_unlocked(), getchar_unlocked(), putc_unlocked(), @@ -901,9 +901,9 @@ _POSIX_THREAD_SAFE_FUNCTIONS asctime_r(), ctime_r(), - flockfile(), - ftrylockfile(), - funlockfile(), + flockfile(), yes + ftrylockfile(), yes + funlockfile(), yes getc_unlocked(), getchar_unlocked(), getgrgid_r(), diff --git a/lib/libc/Kconfig b/lib/libc/Kconfig index 6607f62c521f07..8794c8a898bc81 100644 --- a/lib/libc/Kconfig +++ b/lib/libc/Kconfig @@ -75,6 +75,7 @@ config MINIMAL_LIBC imply COMMON_LIBC_MALLOC imply COMMON_LIBC_CALLOC imply COMMON_LIBC_REALLOCARRAY + select POSIX_FILE_LOCKING if POSIX_THREAD_SAFE_FUNCTIONS help Build with minimal C library. diff --git a/lib/libc/minimal/include/stdio.h b/lib/libc/minimal/include/stdio.h index db3c89790c42ae..995bf255ece8f3 100644 --- a/lib/libc/minimal/include/stdio.h +++ b/lib/libc/minimal/include/stdio.h @@ -63,6 +63,12 @@ size_t fwrite(const void *ZRESTRICT ptr, size_t size, size_t nitems, #define putc(c, stream) fputc(c, stream) #define putchar(c) putc(c, stdout) +#if defined(CONFIG_POSIX_FILE_LOCKING) || defined(__DOXYGEN__) +void flockfile(FILE *file); +int ftrylockfile(FILE *file); +void funlockfile(FILE *file); +#endif /* CONFIG_POSIX_FILE_LOCKING || __DOXYGEN__ */ + #ifdef __cplusplus } #endif diff --git a/lib/os/fdtable.c b/lib/os/fdtable.c index 534e212aa6465f..b4536370ddafed 100644 --- a/lib/os/fdtable.c +++ b/lib/os/fdtable.c @@ -392,6 +392,35 @@ int zvfs_fsync(int fd) return z_fdtable_call_ioctl(fdtable[fd].vtable, fdtable[fd].obj, ZFD_IOCTL_FSYNC); } +#if defined(CONFIG_POSIX_FILE_LOCKING) +void zvfs_flockfile(int fd) +{ + if (_check_fd(fd) < 0) { + return; + } + + (void)k_mutex_lock(&fdtable[fd].lock, K_FOREVER); +} + +int zvfs_ftrylockfile(int fd) +{ + if (_check_fd(fd) < 0) { + return -1; + } + + return k_mutex_lock(&fdtable[fd].lock, K_NO_WAIT); +} + +void zvfs_funlockfile(int fd) +{ + if (_check_fd(fd) < 0) { + return; + } + + (void)k_mutex_unlock(&fdtable[fd].lock); +} +#endif /* CONFIG_POSIX_FILE_LOCKING */ + static inline off_t zvfs_lseek_wrap(int fd, int cmd, ...) { off_t res; diff --git a/lib/posix/options/CMakeLists.txt b/lib/posix/options/CMakeLists.txt index f7da027223d4d7..6fddb9ed8ec7d3 100644 --- a/lib/posix/options/CMakeLists.txt +++ b/lib/posix/options/CMakeLists.txt @@ -55,6 +55,7 @@ zephyr_library_sources_ifdef(CONFIG_POSIX_MEMLOCK mlockall.c) zephyr_library_sources_ifdef(CONFIG_POSIX_MEMLOCK_RANGE mlock.c) zephyr_library_sources_ifdef(CONFIG_POSIX_MEMORY_PROTECTION mprotect.c) zephyr_library_sources_ifdef(CONFIG_POSIX_MAPPED_FILES mmap.c) +zephyr_library_sources_ifdef(CONFIG_POSIX_FILE_LOCKING file_locking.c) zephyr_library_sources_ifdef(CONFIG_POSIX_MESSAGE_PASSING mqueue.c) zephyr_library_sources_ifdef(CONFIG_POSIX_MULTI_PROCESS sleep.c diff --git a/lib/posix/options/Kconfig b/lib/posix/options/Kconfig index bdf0ed4901f051..07b76410a03c65 100644 --- a/lib/posix/options/Kconfig +++ b/lib/posix/options/Kconfig @@ -13,6 +13,7 @@ rsource "Kconfig.barrier" rsource "Kconfig.c_lib_ext" rsource "Kconfig.device_io" rsource "Kconfig.fd_mgmt" +rsource "Kconfig.file_locking" rsource "Kconfig.fs" rsource "Kconfig.mem" rsource "Kconfig.mqueue" diff --git a/lib/posix/options/Kconfig.file_locking b/lib/posix/options/Kconfig.file_locking new file mode 100644 index 00000000000000..7a731a592ce6b5 --- /dev/null +++ b/lib/posix/options/Kconfig.file_locking @@ -0,0 +1,38 @@ +# Copyright (c) 2024 Meta Platforms +# +# SPDX-License-Identifier: Apache-2.0 + +config POSIX_FILE_LOCKING + bool "POSIX file locking [EXPERIMENTAL]" + select EXPERIMENTAL + select FDTABLE + help + Select 'y' here and Zephyr will provide implementations for the POSIX_FILE_LOCKING Option + Group. + This includes support for flockfile(), ftrylockfile(), funlockfile(), getc_unlocked(), + getchar_unlocked(), putc_unlocked() and putchar_unlocked(). + + For more information, please see + https://pubs.opengroup.org/onlinepubs/9699919799/xrat/V4_subprofiles.html + +if POSIX_FILE_LOCKING + +# These options are intended to be used for compatibility with external POSIX +# implementations such as those in Newlib or Picolibc. + +config POSIX_FD_MGMT_ALIAS_FLOCKFILE + bool + help + Select 'y' here and Zephyr will provide an alias for flockfile() as _flockfile(). + +config POSIX_FD_MGMT_ALIAS_FTRYLOCKFILE + bool + help + Select 'y' here and Zephyr will provide an alias for ftrylockfile() as _ftrylockfile(). + +config POSIX_FD_MGMT_ALIAS_FUNLOCKFILE + bool + help + Select 'y' here and Zephyr will provide an alias for funlockfile() as _funlockfile(). + +endif # POSIX_FILE_LOCKING diff --git a/lib/posix/options/Kconfig.pthread b/lib/posix/options/Kconfig.pthread index ec38286ff49a3e..c779d2f2c8ac96 100644 --- a/lib/posix/options/Kconfig.pthread +++ b/lib/posix/options/Kconfig.pthread @@ -156,6 +156,7 @@ config POSIX_THREAD_PRIO_PROTECT config POSIX_THREAD_SAFE_FUNCTIONS bool "POSIX thread-safe functions" + select POSIX_FILE_LOCKING help Select 'y' here to enable POSIX thread-safe functions including asctime_r(), ctime_r(), flockfile(), ftrylockfile(), funlockfile(), getc_unlocked(), getchar_unlocked(), diff --git a/lib/posix/options/file_locking.c b/lib/posix/options/file_locking.c new file mode 100644 index 00000000000000..755d1194828b59 --- /dev/null +++ b/lib/posix/options/file_locking.c @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2024 Meta Platforms + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include + +void zvfs_flockfile(int fd); +int zvfs_ftrylockfile(int fd); +void zvfs_funlockfile(int fd); + +void flockfile(FILE *file) +{ + zvfs_flockfile(POINTER_TO_INT(file)); +} +#ifdef CONFIG_POSIX_FD_MGMT_ALIAS_FLOCKFILE +FUNC_ALIAS(flockfile, _flockfile, void); +#endif /* CONFIG_POSIX_FD_MGMT_ALIAS_FLOCKFILE */ + +int ftrylockfile(FILE *file) +{ + return zvfs_ftrylockfile(POINTER_TO_INT(file)); +} +#ifdef CONFIG_POSIX_FD_MGMT_ALIAS_FTRYLOCKFILE +FUNC_ALIAS(ftrylockfile, _ftrylockfile, int); +#endif /* CONFIG_POSIX_FD_MGMT_ALIAS_FTRYLOCKFILE */ + +void funlockfile(FILE *file) +{ + zvfs_funlockfile(POINTER_TO_INT(file)); +} +#ifdef CONFIG_POSIX_FD_MGMT_ALIAS_FUNLOCKFILE +FUNC_ALIAS(funlockfile, _funlockfile, void); +#endif /* CONFIG_POSIX_FD_MGMT_ALIAS_FUNLOCKFILE */ diff --git a/tests/posix/common/src/file_locking.c b/tests/posix/common/src/file_locking.c new file mode 100644 index 00000000000000..ade9ecf908471c --- /dev/null +++ b/tests/posix/common/src/file_locking.c @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2024, Meta Platforms + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include +#include + +#ifndef CONFIG_PICOLIBC + +K_THREAD_STACK_DEFINE(test_stack, 1024); + +#define LOCK_SHOULD_PASS (void *)true +#define LOCK_SHOULD_FAIL (void *)false +#define UNLOCK_FILE (void *)true +#define NO_UNLOCK_FILE (void *)false + +void ftrylockfile_thread(void *p1, void *p2, void *p3) +{ + int ret; + FILE *file = p1; + bool success = (bool)p2; + bool unlock = (bool)p3; + + if (success) { + ret = ftrylockfile(file); + zassert_ok(ret, "Expected ftrylockfile to succeed but it failed: %d", ret); + if (unlock) { + funlockfile(file); + } + } else { + zassert_not_ok(ftrylockfile(file), + "Expected ftrylockfile to failed but it succeeded"); + } +} + +void flockfile_thread(void *p1, void *p2, void *p3) +{ + FILE *file = p1; + bool success = (bool)p2; + bool unlock = (bool)p3; + + flockfile(file); + + if (!success) { + /* Shouldn't be here if it supposed to fail */ + ztest_test_fail(); + } + + if (unlock) { + funlockfile(file); + } +} + +ZTEST(file_locking, test_file_locking) +{ + FILE *file = INT_TO_POINTER(z_alloc_fd(NULL, NULL)); + int priority = k_thread_priority_get(k_current_get()); + struct k_thread test_thread; + + /* Lock 5 times with flockfile */ + for (int i = 0; i < 5; i++) { + flockfile(file); + } + + /* Lock 5 times with ftrylockfile */ + for (int i = 0; i < 5; i++) { + zassert_ok(ftrylockfile(file)); + } + + /* Spawn a thread that uses ftrylockfile(), it should fail immediately */ + k_thread_create(&test_thread, test_stack, K_THREAD_STACK_SIZEOF(test_stack), + ftrylockfile_thread, file, LOCK_SHOULD_FAIL, NO_UNLOCK_FILE, priority, 0, + K_NO_WAIT); + /* The thread should terminate immediately */ + zassert_ok(k_thread_join(&test_thread, K_MSEC(100))); + + /* Try agian with flockfile(), it should block forever */ + k_thread_create(&test_thread, test_stack, K_THREAD_STACK_SIZEOF(test_stack), + flockfile_thread, file, LOCK_SHOULD_FAIL, NO_UNLOCK_FILE, priority, 0, + K_NO_WAIT); + /* We expect the flockfile() call to block forever, so this will timeout */ + zassert_equal(k_thread_join(&test_thread, K_MSEC(500)), -EAGAIN); + /* Abort the test thread */ + k_thread_abort(&test_thread); + + /* Unlock the file completely in this thread */ + for (int i = 0; i < 10; i++) { + funlockfile(file); + } + + /* Spawn the thread again, which should be able to lock the file now with ftrylockfile() */ + k_thread_create(&test_thread, test_stack, K_THREAD_STACK_SIZEOF(test_stack), + ftrylockfile_thread, file, LOCK_SHOULD_PASS, UNLOCK_FILE, priority, 0, + K_NO_WAIT); + zassert_ok(k_thread_join(&test_thread, K_MSEC(100))); + + z_free_fd(POINTER_TO_INT(file)); +} + +#else +/** + * PicoLIBC doesn't support these functions in its header + * Skip the tests for now. + */ +ZTEST(file_locking, test_file_locking) +{ + ztest_test_skip(); +} +#endif /* CONFIG_PICOLIBC */ + +ZTEST_SUITE(file_locking, NULL, NULL, NULL, NULL, NULL);