Skip to content

Commit 87729ba

Browse files
committed
posix: implement file locking functions
Implement file locking functions and added testcase. Signed-off-by: Yong Cong Sin <ycsin@meta.com>
1 parent af57038 commit 87729ba

File tree

10 files changed

+236
-6
lines changed

10 files changed

+236
-6
lines changed

doc/services/portability/posix/option_groups/index.rst

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -564,9 +564,9 @@ This table lists service support status in Zephyr for `POSIX_FD_MGMT`:
564564
:header: API, Supported
565565
:widths: 50,10
566566

567-
flockfile(),
568-
ftrylockfile(),
569-
funlockfile(),
567+
flockfile(), yes
568+
ftrylockfile(), yes
569+
funlockfile(), yes
570570
getc_unlocked(),
571571
getchar_unlocked(),
572572
putc_unlocked(),
@@ -901,9 +901,9 @@ _POSIX_THREAD_SAFE_FUNCTIONS
901901

902902
asctime_r(),
903903
ctime_r(),
904-
flockfile(),
905-
ftrylockfile(),
906-
funlockfile(),
904+
flockfile(), yes
905+
ftrylockfile(), yes
906+
funlockfile(), yes
907907
getc_unlocked(),
908908
getchar_unlocked(),
909909
getgrgid_r(),

lib/libc/Kconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ config MINIMAL_LIBC
7575
imply COMMON_LIBC_MALLOC
7676
imply COMMON_LIBC_CALLOC
7777
imply COMMON_LIBC_REALLOCARRAY
78+
select POSIX_FILE_LOCKING if POSIX_THREAD_SAFE_FUNCTIONS
7879
help
7980
Build with minimal C library.
8081

lib/libc/minimal/include/stdio.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,12 @@ size_t fwrite(const void *ZRESTRICT ptr, size_t size, size_t nitems,
6363
#define putc(c, stream) fputc(c, stream)
6464
#define putchar(c) putc(c, stdout)
6565

66+
#if defined(CONFIG_POSIX_FILE_LOCKING) || defined(__DOXYGEN__)
67+
void flockfile(FILE *file);
68+
int ftrylockfile(FILE *file);
69+
void funlockfile(FILE *file);
70+
#endif /* CONFIG_POSIX_FILE_LOCKING || __DOXYGEN__ */
71+
6672
#ifdef __cplusplus
6773
}
6874
#endif

lib/os/fdtable.c

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,35 @@ int zvfs_fsync(int fd)
392392
return z_fdtable_call_ioctl(fdtable[fd].vtable, fdtable[fd].obj, ZFD_IOCTL_FSYNC);
393393
}
394394

395+
#if defined(CONFIG_POSIX_FILE_LOCKING)
396+
void zvfs_flockfile(int fd)
397+
{
398+
if (_check_fd(fd) < 0) {
399+
return;
400+
}
401+
402+
(void)k_mutex_lock(&fdtable[fd].lock, K_FOREVER);
403+
}
404+
405+
int zvfs_ftrylockfile(int fd)
406+
{
407+
if (_check_fd(fd) < 0) {
408+
return -1;
409+
}
410+
411+
return k_mutex_lock(&fdtable[fd].lock, K_NO_WAIT);
412+
}
413+
414+
void zvfs_funlockfile(int fd)
415+
{
416+
if (_check_fd(fd) < 0) {
417+
return;
418+
}
419+
420+
(void)k_mutex_unlock(&fdtable[fd].lock);
421+
}
422+
#endif /* CONFIG_POSIX_FILE_LOCKING */
423+
395424
static inline off_t zvfs_lseek_wrap(int fd, int cmd, ...)
396425
{
397426
off_t res;

lib/posix/options/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ zephyr_library_sources_ifdef(CONFIG_POSIX_MEMLOCK mlockall.c)
5555
zephyr_library_sources_ifdef(CONFIG_POSIX_MEMLOCK_RANGE mlock.c)
5656
zephyr_library_sources_ifdef(CONFIG_POSIX_MEMORY_PROTECTION mprotect.c)
5757
zephyr_library_sources_ifdef(CONFIG_POSIX_MAPPED_FILES mmap.c)
58+
zephyr_library_sources_ifdef(CONFIG_POSIX_FILE_LOCKING file_locking.c)
5859
zephyr_library_sources_ifdef(CONFIG_POSIX_MESSAGE_PASSING mqueue.c)
5960
zephyr_library_sources_ifdef(CONFIG_POSIX_MULTI_PROCESS
6061
sleep.c

lib/posix/options/Kconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ rsource "Kconfig.barrier"
1313
rsource "Kconfig.c_lib_ext"
1414
rsource "Kconfig.device_io"
1515
rsource "Kconfig.fd_mgmt"
16+
rsource "Kconfig.file_locking"
1617
rsource "Kconfig.fs"
1718
rsource "Kconfig.mem"
1819
rsource "Kconfig.mqueue"
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# Copyright (c) 2024 Meta Platforms
2+
#
3+
# SPDX-License-Identifier: Apache-2.0
4+
5+
config POSIX_FILE_LOCKING
6+
bool "POSIX file locking [EXPERIMENTAL]"
7+
select EXPERIMENTAL
8+
select FDTABLE
9+
help
10+
Select 'y' here and Zephyr will provide implementations for the POSIX_FILE_LOCKING Option
11+
Group.
12+
This includes support for flockfile(), ftrylockfile(), funlockfile(), getc_unlocked(),
13+
getchar_unlocked(), putc_unlocked() and putchar_unlocked().
14+
15+
For more information, please see
16+
https://pubs.opengroup.org/onlinepubs/9699919799/xrat/V4_subprofiles.html
17+
18+
if POSIX_FILE_LOCKING
19+
20+
# These options are intended to be used for compatibility with external POSIX
21+
# implementations such as those in Newlib or Picolibc.
22+
23+
config POSIX_FD_MGMT_ALIAS_FLOCKFILE
24+
bool
25+
help
26+
Select 'y' here and Zephyr will provide an alias for flockfile() as _flockfile().
27+
28+
config POSIX_FD_MGMT_ALIAS_FTRYLOCKFILE
29+
bool
30+
help
31+
Select 'y' here and Zephyr will provide an alias for ftrylockfile() as _ftrylockfile().
32+
33+
config POSIX_FD_MGMT_ALIAS_FUNLOCKFILE
34+
bool
35+
help
36+
Select 'y' here and Zephyr will provide an alias for funlockfile() as _funlockfile().
37+
38+
endif # POSIX_FILE_LOCKING

lib/posix/options/Kconfig.pthread

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ config POSIX_THREAD_PRIO_PROTECT
156156

157157
config POSIX_THREAD_SAFE_FUNCTIONS
158158
bool "POSIX thread-safe functions"
159+
select POSIX_FILE_LOCKING
159160
help
160161
Select 'y' here to enable POSIX thread-safe functions including asctime_r(), ctime_r(),
161162
flockfile(), ftrylockfile(), funlockfile(), getc_unlocked(), getchar_unlocked(),

lib/posix/options/file_locking.c

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Copyright (c) 2024 Meta Platforms
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#include <stdio.h>
8+
9+
#include <zephyr/sys/util.h>
10+
11+
void zvfs_flockfile(int fd);
12+
int zvfs_ftrylockfile(int fd);
13+
void zvfs_funlockfile(int fd);
14+
15+
void flockfile(FILE *file)
16+
{
17+
zvfs_flockfile(POINTER_TO_INT(file));
18+
}
19+
#ifdef CONFIG_POSIX_FD_MGMT_ALIAS_FLOCKFILE
20+
FUNC_ALIAS(flockfile, _flockfile, void);
21+
#endif /* CONFIG_POSIX_FD_MGMT_ALIAS_FLOCKFILE */
22+
23+
int ftrylockfile(FILE *file)
24+
{
25+
return zvfs_ftrylockfile(POINTER_TO_INT(file));
26+
}
27+
#ifdef CONFIG_POSIX_FD_MGMT_ALIAS_FTRYLOCKFILE
28+
FUNC_ALIAS(ftrylockfile, _ftrylockfile, int);
29+
#endif /* CONFIG_POSIX_FD_MGMT_ALIAS_FTRYLOCKFILE */
30+
31+
void funlockfile(FILE *file)
32+
{
33+
zvfs_funlockfile(POINTER_TO_INT(file));
34+
}
35+
#ifdef CONFIG_POSIX_FD_MGMT_ALIAS_FUNLOCKFILE
36+
FUNC_ALIAS(funlockfile, _funlockfile, void);
37+
#endif /* CONFIG_POSIX_FD_MGMT_ALIAS_FUNLOCKFILE */

tests/posix/common/src/file_locking.c

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
/*
2+
* Copyright (c) 2024, Meta Platforms
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#include <errno.h>
8+
#include <stdio.h>
9+
10+
#include <zephyr/sys/fdtable.h>
11+
#include <zephyr/ztest.h>
12+
13+
#ifndef CONFIG_PICOLIBC
14+
15+
K_THREAD_STACK_DEFINE(test_stack, 1024);
16+
17+
#define LOCK_SHOULD_PASS (void *)true
18+
#define LOCK_SHOULD_FAIL (void *)false
19+
#define UNLOCK_FILE (void *)true
20+
#define NO_UNLOCK_FILE (void *)false
21+
22+
void ftrylockfile_thread(void *p1, void *p2, void *p3)
23+
{
24+
int ret;
25+
FILE *file = p1;
26+
bool success = (bool)p2;
27+
bool unlock = (bool)p3;
28+
29+
if (success) {
30+
ret = ftrylockfile(file);
31+
zassert_ok(ret, "Expected ftrylockfile to succeed but it failed: %d", ret);
32+
if (unlock) {
33+
funlockfile(file);
34+
}
35+
} else {
36+
zassert_not_ok(ftrylockfile(file),
37+
"Expected ftrylockfile to failed but it succeeded");
38+
}
39+
}
40+
41+
void flockfile_thread(void *p1, void *p2, void *p3)
42+
{
43+
FILE *file = p1;
44+
bool success = (bool)p2;
45+
bool unlock = (bool)p3;
46+
47+
flockfile(file);
48+
49+
if (!success) {
50+
/* Shouldn't be here if it supposed to fail */
51+
ztest_test_fail();
52+
}
53+
54+
if (unlock) {
55+
funlockfile(file);
56+
}
57+
}
58+
59+
ZTEST(file_locking, test_file_locking)
60+
{
61+
FILE *file = INT_TO_POINTER(z_alloc_fd(NULL, NULL));
62+
int priority = k_thread_priority_get(k_current_get());
63+
struct k_thread test_thread;
64+
65+
/* Lock 5 times with flockfile */
66+
for (int i = 0; i < 5; i++) {
67+
flockfile(file);
68+
}
69+
70+
/* Lock 5 times with ftrylockfile */
71+
for (int i = 0; i < 5; i++) {
72+
zassert_ok(ftrylockfile(file));
73+
}
74+
75+
/* Spawn a thread that uses ftrylockfile(), it should fail immediately */
76+
k_thread_create(&test_thread, test_stack, K_THREAD_STACK_SIZEOF(test_stack),
77+
ftrylockfile_thread, file, LOCK_SHOULD_FAIL, NO_UNLOCK_FILE, priority, 0,
78+
K_NO_WAIT);
79+
/* The thread should terminate immediately */
80+
zassert_ok(k_thread_join(&test_thread, K_MSEC(100)));
81+
82+
/* Try agian with flockfile(), it should block forever */
83+
k_thread_create(&test_thread, test_stack, K_THREAD_STACK_SIZEOF(test_stack),
84+
flockfile_thread, file, LOCK_SHOULD_FAIL, NO_UNLOCK_FILE, priority, 0,
85+
K_NO_WAIT);
86+
/* We expect the flockfile() call to block forever, so this will timeout */
87+
zassert_equal(k_thread_join(&test_thread, K_MSEC(500)), -EAGAIN);
88+
/* Abort the test thread */
89+
k_thread_abort(&test_thread);
90+
91+
/* Unlock the file completely in this thread */
92+
for (int i = 0; i < 10; i++) {
93+
funlockfile(file);
94+
}
95+
96+
/* Spawn the thread again, which should be able to lock the file now with ftrylockfile() */
97+
k_thread_create(&test_thread, test_stack, K_THREAD_STACK_SIZEOF(test_stack),
98+
ftrylockfile_thread, file, LOCK_SHOULD_PASS, UNLOCK_FILE, priority, 0,
99+
K_NO_WAIT);
100+
zassert_ok(k_thread_join(&test_thread, K_MSEC(100)));
101+
102+
z_free_fd(POINTER_TO_INT(file));
103+
}
104+
105+
#else
106+
/**
107+
* PicoLIBC doesn't support these functions in its header
108+
* Skip the tests for now.
109+
*/
110+
ZTEST(file_locking, test_file_locking)
111+
{
112+
ztest_test_skip();
113+
}
114+
#endif /* CONFIG_PICOLIBC */
115+
116+
ZTEST_SUITE(file_locking, NULL, NULL, NULL, NULL, NULL);

0 commit comments

Comments
 (0)