Skip to content

Commit 820c377

Browse files
cfriedtJakub Klimczak
andcommitted
zvfs: improve libc FILE to integer fd 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 count 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 1. FILE *z_libc_file_alloc(int fd, const char *mode) Allocate and populate the required fields of a FILE object 2. int z_libc_file_get_fd(FILE *fp) Convert a FILE* object to an integer file descriptor. For Picolibc and Newlib-based C libraries, these functions set and get the integer file descriptor from a field of the internal FILE object representation. For the minimal C library, these functions convert between array index and struct fd_entry pointers. Includes changes necessary for tests to still pass. Signed-off-by: Chris Friedt <[email protected]> Co-authored-by: Jakub Klimczak <[email protected]> Signed-off-by: Jakub Klimczak <[email protected]>
1 parent 945133e commit 820c377

File tree

21 files changed

+446
-21
lines changed

21 files changed

+446
-21
lines changed

include/zephyr/posix/unistd.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@ extern "C" {
2525
/* File related operations */
2626
int close(int file);
2727
ssize_t write(int file, const void *buffer, size_t count);
28+
ssize_t pwrite(int file, const void *buffer, size_t count, off_t offset);
2829
ssize_t read(int file, void *buffer, size_t count);
30+
ssize_t pread(int fd, void *buf, size_t count, off_t offset);
2931
off_t lseek(int file, off_t offset, int whence);
3032
int fsync(int fd);
3133
int ftruncate(int fd, off_t length);

include/zephyr/sys/fdtable.h

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,20 @@
1414
#include <zephyr/kernel.h>
1515
#include <zephyr/sys/util.h>
1616

17+
/* FIXME: use k_off_t and k_ssize_t to avoid the POSIX->Zephyr->POSIX dependency cycle */
18+
#ifdef CONFIG_NEWLIB_LIBC
19+
#ifndef _OFF_T_DECLARED
20+
typedef _off_t off_t;
21+
#define _OFF_T_DECLARED
22+
#endif
23+
#ifndef _SSIZE_T_DECLARED
24+
typedef _ssize_t ssize_t;
25+
#define _SSIZE_T_DECLARED
26+
#endif
27+
#endif
28+
29+
#include <stdio.h>
30+
1731
#ifdef CONFIG_PICOLIBC
1832
#define ZVFS_O_APPEND 0x0400
1933
#define ZVFS_O_CREAT 0x0040
@@ -60,18 +74,6 @@
6074
extern "C" {
6175
#endif
6276

63-
/* FIXME: use k_off_t and k_ssize_t to avoid the POSIX->Zephyr->POSIX dependency cycle */
64-
#ifdef CONFIG_NEWLIB_LIBC
65-
#ifndef _OFF_T_DECLARED
66-
typedef __off_t off_t;
67-
#define _OFF_T_DECLARED
68-
#endif
69-
#ifndef _SSIZE_T_DECLARED
70-
typedef _ssize_t ssize_t;
71-
#define _SSIZE_T_DECLARED
72-
#endif
73-
#endif
74-
7577
/**
7678
* File descriptor virtual method table.
7779
* Currently all operations beyond read/write/close go thru ioctl method.
@@ -261,6 +263,12 @@ __syscall int zvfs_select(int nfds, struct zvfs_fd_set *ZRESTRICT readfds,
261263
struct zvfs_fd_set *ZRESTRICT errorfds,
262264
const struct timespec *ZRESTRICT timeout, const void *ZRESTRICT sigmask);
263265

266+
void zvfs_libc_file_alloc_cb(int fd, const char *mode, FILE *fp);
267+
int zvfs_libc_file_alloc(int fd, const char *mode, FILE **fp, k_timeout_t timeout);
268+
void zvfs_libc_file_free(FILE *fp);
269+
int zvfs_libc_file_get_fd(FILE *fp);
270+
FILE *zvfs_libc_file_from_fd(int fd);
271+
264272
/**
265273
* Request codes for fd_op_vtable.ioctl().
266274
*

lib/libc/common/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ zephyr_library_sources_ifdef(CONFIG_COMMON_LIBC_THRD
2020
source/thrd/tss.c
2121
)
2222
zephyr_library_sources_ifdef(CONFIG_COMMON_LIBC_REMOVE source/stdio/remove.c)
23+
zephyr_library_sources_ifdef(CONFIG_COMMON_LIBC_FCLOSE source/stdio/fclose.c)
2324

2425
# Prevent compiler from optimizing calloc into an infinite recursive call
2526
zephyr_library_compile_options($<TARGET_PROPERTY:compiler,no_builtin_malloc>)

lib/libc/common/Kconfig

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ config COMMON_LIBC_THRD
109109
depends on DYNAMIC_THREAD
110110
# Note: the POSIX_API dependency is only necessary until common elements
111111
# of C11 threads and POSIX API can be abstracted out to a common library.
112-
depends on POSIX_API
112+
depends on POSIX_THREADS
113113
default y
114114
help
115115
Common implementation of C11 <threads.h> API.
@@ -120,3 +120,9 @@ config COMMON_LIBC_REMOVE
120120
default y if FILE_SYSTEM
121121
help
122122
Common implementation of remove().
123+
124+
config COMMON_LIBC_FCLOSE
125+
bool "Common C library fclose"
126+
default y if ZVFS && !MINIMAL_LIBC
127+
help
128+
Enable the common C library implementation of fclose()
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* Copyright (c) 2025 Antmicro <www.antmicro.com>
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#include <zephyr/sys/fdtable.h>
8+
#include <unistd.h>
9+
10+
int fclose(FILE *stream)
11+
{
12+
int fd;
13+
14+
fflush(stream);
15+
fd = zvfs_libc_file_get_fd(stream);
16+
17+
if (!fd) {
18+
return EOF;
19+
}
20+
if (close(fd)) {
21+
return EOF;
22+
}
23+
24+
return 0;
25+
}

lib/libc/newlib/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
zephyr_library()
44
zephyr_library_sources(libc-hooks.c)
55

6+
zephyr_library_sources_ifdef(CONFIG_ZVFS zvfs_libc.c)
7+
68
# Do not allow LTO when compiling libc-hooks.c file
79
set_source_files_properties(libc-hooks.c PROPERTIES COMPILE_OPTIONS $<TARGET_PROPERTY:compiler,prohibit_lto>)
810

lib/libc/newlib/Kconfig

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,4 +80,12 @@ config NEWLIB_LIBC_USE_POSIX_LIMITS_H
8080
Disable this option if your toolchain's newlib already includes all mandatory POSIX
8181
limits.
8282

83+
if ZVFS
84+
85+
config ZVFS_LIBC_FILE_SIZE
86+
default 184 if 64BIT
87+
default 104
88+
89+
endif # ZVFS
90+
8391
endif # NEWLIB_LIBC

lib/libc/newlib/libc-hooks.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,9 @@ static int (*_stdout_hook)(int) = _stdout_hook_default;
169169
void __stdout_hook_install(int (*hook)(int))
170170
{
171171
_stdout_hook = hook;
172+
#ifdef CONFIG_ZVFS
173+
stdout->_file = 1; /* STDOUT_FILENO */
174+
#endif
172175
}
173176

174177
static unsigned char _stdin_hook_default(void)
@@ -181,6 +184,9 @@ static unsigned char (*_stdin_hook)(void) = _stdin_hook_default;
181184
void __stdin_hook_install(unsigned char (*hook)(void))
182185
{
183186
_stdin_hook = hook;
187+
#ifdef CONFIG_ZVFS
188+
stdin->_file = 0; /* STDIN_FILENO */
189+
#endif
184190
}
185191

186192
int z_impl_zephyr_read_stdin(char *buf, int nbytes)

lib/libc/newlib/zvfs_libc.c

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/*
2+
* Copyright (c) 2024 Tenstorrent AI ULC
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#include <stddef.h>
8+
#include <stdio.h>
9+
#include <stdlib.h>
10+
11+
#include <zephyr/sys/fdtable.h>
12+
#include <zephyr/toolchain.h>
13+
14+
BUILD_ASSERT(CONFIG_ZVFS_LIBC_FILE_SIZE >= sizeof(__FILE),
15+
"CONFIG_ZVFS_LIBC_FILE_SIZE must at least match size of FILE object");
16+
BUILD_ASSERT(CONFIG_ZVFS_LIBC_FILE_ALIGN == __alignof(__FILE),
17+
"CONFIG_ZVFS_LIBC_FILE_SIZE must match alignment of FILE object");
18+
19+
static int z_libc_sflags(const char *mode)
20+
{
21+
int ret = 0;
22+
23+
switch (mode[0]) {
24+
case 'r':
25+
ret = ZVFS_O_RDONLY;
26+
break;
27+
28+
case 'w':
29+
ret = ZVFS_O_WRONLY | ZVFS_O_CREAT | ZVFS_O_TRUNC;
30+
break;
31+
32+
case 'a':
33+
ret = ZVFS_O_WRONLY | ZVFS_O_CREAT | ZVFS_O_APPEND;
34+
break;
35+
default:
36+
return 0;
37+
}
38+
39+
while (*++mode) {
40+
switch (*mode) {
41+
case '+':
42+
ret |= ZVFS_O_RDWR;
43+
break;
44+
case 'x':
45+
ret |= ZVFS_O_EXCL;
46+
break;
47+
default:
48+
break;
49+
}
50+
}
51+
52+
return ret;
53+
}
54+
55+
void zvfs_libc_file_alloc_cb(int fd, const char *mode, FILE *fp)
56+
{
57+
/*
58+
* These symbols have conflicting declarations in upstream headers.
59+
* Use uintptr_t and a cast to assign cleanly.
60+
*/
61+
extern uintptr_t _close_r;
62+
extern uintptr_t _lseek_r;
63+
extern uintptr_t _read_r;
64+
extern uintptr_t _write_r;
65+
66+
__ASSERT_NO_MSG(mode != NULL);
67+
__ASSERT_NO_MSG(fp != NULL);
68+
69+
fp->_flags = z_libc_sflags(mode);
70+
fp->_file = fd;
71+
fp->_cookie = (void *)fp;
72+
73+
*(uintptr_t *)&fp->_read = (uintptr_t)&_read_r;
74+
*(uintptr_t *)&fp->_write = (uintptr_t)&_write_r;
75+
*(uintptr_t *)&fp->_seek = (uintptr_t)&_lseek_r;
76+
*(uintptr_t *)&fp->_close = (uintptr_t)&_close_r;
77+
}
78+
79+
int zvfs_libc_file_get_fd(FILE *fp)
80+
{
81+
return fp->_file;
82+
}

lib/libc/picolibc/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ zephyr_library_sources(
1111
stdio.c
1212
)
1313

14+
zephyr_library_sources_ifdef(CONFIG_ZVFS zvfs_libc.c)
15+
1416
zephyr_library_compile_options($<TARGET_PROPERTY:compiler,prohibit_lto>)
1517

1618
# define __LINUX_ERRNO_EXTENSIONS__ so we get errno defines like -ESHUTDOWN

0 commit comments

Comments
 (0)