Skip to content

Commit

Permalink
posix: implement file locking functions
Browse files Browse the repository at this point in the history
Implement file locking functions and added testcase.

Signed-off-by: Yong Cong Sin <ycsin@meta.com>
  • Loading branch information
ycsin committed Jun 24, 2024
1 parent af57038 commit 87729ba
Show file tree
Hide file tree
Showing 10 changed files with 236 additions and 6 deletions.
12 changes: 6 additions & 6 deletions doc/services/portability/posix/option_groups/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down Expand Up @@ -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(),
Expand Down
1 change: 1 addition & 0 deletions lib/libc/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
6 changes: 6 additions & 0 deletions lib/libc/minimal/include/stdio.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
29 changes: 29 additions & 0 deletions lib/os/fdtable.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions lib/posix/options/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions lib/posix/options/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
38 changes: 38 additions & 0 deletions lib/posix/options/Kconfig.file_locking
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions lib/posix/options/Kconfig.pthread
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
37 changes: 37 additions & 0 deletions lib/posix/options/file_locking.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright (c) 2024 Meta Platforms
*
* SPDX-License-Identifier: Apache-2.0
*/

#include <stdio.h>

#include <zephyr/sys/util.h>

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 */
116 changes: 116 additions & 0 deletions tests/posix/common/src/file_locking.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/*
* Copyright (c) 2024, Meta Platforms
*
* SPDX-License-Identifier: Apache-2.0
*/

#include <errno.h>
#include <stdio.h>

#include <zephyr/sys/fdtable.h>
#include <zephyr/ztest.h>

#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);

0 comments on commit 87729ba

Please sign in to comment.