feat(freertos/vfs): add Linux cooperative syscall dispatch with VFS FD registration

Rework the syscall interposition architecture so FreeRTOS works
standalone (without VFS) and VFS optionally overrides with strong
symbols.  All kernel FDs are registered with VFS via
esp_vfs_register_fd_with_local_fd, so the application only sees
VFS-allocated FD numbers, preventing numerical collisions between
kernel FDs and VFS-internal FD slots.

FreeRTOS side:
- Create linux_port_coop_internal.h with LINUX_COOP_IO_LOOP,
  LINUX_COOP_RESOLVE, linux_coop_yield and FD state table functions
- Export cooperative I/O primitives (freertos_linux_coop_read/write/
  open/close/fcntl/select/pread/pwrite/readv/writev/recv/send/
  recvfrom/sendto/recvmsg/sendmsg/connect/accept/pselect/poll/
  socket/socketpair/pipe/pipe2/dup/dup2/syscalls_init)
- Define weak POSIX symbols calling through to the cooperative
  primitives; keep nanosleep/sleep/usleep as strong (not FD-related)
- Refactor freertos_linux_coop_syscalls.h into a pure public API header

VFS side:
- Register the Linux host FS with esp_vfs_register_fs_with_id() as a
  proper VFS driver; register stdin/stdout/stderr at init (priority 99)
- Rewrite vfs_linux.c with strong POSIX symbols dispatching through
  esp_vfs_*
- Add strong overrides for FD-creating syscalls (open, pipe, pipe2,
  socket, socketpair, dup, dup2, accept) that register returned FDs
  with VFS
- Add strong overrides for FD-translating syscalls (readv, writev,
  recv, send, recvfrom, sendto, recvmsg, sendmsg, connect, pselect,
  poll) that translate VFS FD to kernel FD
- Delete vfs_coop_syscalls.c (absorbed into FreeRTOS weak + VFS strong)
- Update CMakeLists.txt: remove vfs_coop_syscalls.c, add linker hook
This commit is contained in:
Guillaume Souchere
2026-06-04 11:36:34 +02:00
parent 78420f2614
commit 9835daba70
13 changed files with 1747 additions and 385 deletions
+15 -2
View File
@@ -10,7 +10,7 @@ if(${target} STREQUAL "linux")
if(CONFIG_VFS_SUPPORT_IO)
list(APPEND inc linux_include)
list(APPEND priv_inc private_include)
list(APPEND srcs "vfs_linux.c" "vfs.c" "vfs_calls.c")
list(APPEND srcs "vfs_linux.c" "vfs_linux_default_coop.c" "vfs.c" "vfs_calls.c")
endif()
if(CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux")
@@ -20,7 +20,20 @@ if(${target} STREQUAL "linux")
idf_component_register(SRCS ${srcs}
LDFRAGMENTS "linker.lf"
INCLUDE_DIRS ${inc}
PRIV_INCLUDE_DIRS ${priv_inc})
PRIV_INCLUDE_DIRS ${priv_inc}
PRIV_REQUIRES freertos)
target_link_libraries(${COMPONENT_LIB} PRIVATE dl)
if(CONFIG_VFS_SUPPORT_IO)
# On macOS, C symbols have a leading underscore prefix.
if(CMAKE_HOST_SYSTEM_NAME STREQUAL "Darwin")
target_link_libraries(${COMPONENT_LIB} INTERFACE "-u _esp_vfs_include_linux_coop_init")
else()
target_link_libraries(${COMPONENT_LIB} INTERFACE "-u esp_vfs_include_linux_coop_init")
endif()
endif()
return()
endif()
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
+1 -1
View File
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
+528 -79
View File
@@ -1,101 +1,83 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @brief Linux target VFS syscall wrappers
* @brief Linux target VFS syscall wrappers (strong POSIX symbols)
*
* This file provides POSIX syscall wrappers that redirect to ESP-IDF VFS functions
* when building for Linux target.
* This file provides strong POSIX symbol definitions that dispatch through
* esp_vfs_* for registered FDs, and fall back to the cooperative syscall
* layer for unregistered FDs (e.g. during early boot before VFS init).
*
* All kernel FDs created by open/pipe/socket/dup/accept are registered
* with VFS via esp_vfs_register_fd_with_local_fd, so the application
* only sees VFS-allocated FD numbers. This eliminates FD collisions
* between kernel FDs and VFS-internal FD allocations.
*
* Init and ops registration live in vfs_linux_default_coop.c.
*/
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/uio.h>
#include <sys/socket.h>
#include <sys/param.h>
#include <poll.h>
#include "esp_vfs.h"
#include "esp_vfs_private.h"
#include "esp_private/freertos_linux_coop_syscalls.h"
int open(const char * path, int flags, ...)
/* Defined in vfs_linux_default_coop.c — set during init_vfs_linux_coop() */
extern esp_vfs_id_t s_linux_host_vfs_id;
/**
* Helper: register a kernel FD with VFS after a successful FD-creating syscall.
* Returns the VFS FD on success, or the raw kernel FD if VFS is not yet initialized.
*/
static int register_kernel_fd(int kernel_fd)
{
struct _reent r;
va_list list;
va_start(list, flags);
int mode = va_arg(list, int);
va_end(list);
return esp_vfs_open(&r, path, flags, mode);
if (kernel_fd < 0) {
return kernel_fd;
}
if (s_linux_host_vfs_id < 0) {
return kernel_fd; /* VFS not initialized yet (early boot) */
}
int vfs_fd;
esp_err_t err = esp_vfs_register_fd_with_local_fd(s_linux_host_vfs_id, kernel_fd, false, &vfs_fd);
if (err != ESP_OK) {
freertos_linux_coop_close(kernel_fd);
errno = ENFILE;
return -1;
}
return vfs_fd;
}
int close(int fd)
/**
* Helper: translate a VFS FD to a kernel FD.
* Returns the kernel (local) FD, or -1 with errno=EBADF.
*/
static int vfs_fd_to_kernel_fd(int fd)
{
struct _reent r;
return esp_vfs_close(&r, fd);
const vfs_entry_t *vfs = get_vfs_for_fd(fd);
if (vfs == NULL) {
return fd; /* Not registered — use directly (early boot) */
}
int local_fd = get_local_fd(vfs, fd);
if (local_fd < 0) {
errno = EBADF;
return -1;
}
return local_fd;
}
ssize_t read(int fd, void * dst, size_t size)
{
struct _reent r;
return esp_vfs_read(&r, fd, dst, size);
}
ssize_t write(int fd, const void * data, size_t size)
{
struct _reent r;
return esp_vfs_write(&r, fd, data, size);
}
off_t lseek(int fd, off_t size, int mode)
{
struct _reent r;
return esp_vfs_lseek(&r, fd, size, mode);
}
ssize_t pread(int fd, void *dst, size_t size, off_t offset)
{
return esp_vfs_pread(fd,dst, size, offset);
}
ssize_t pwrite(int fd, const void *src, size_t size, off_t offset)
{
return esp_vfs_pwrite(fd, src,size, offset);
}
int fstat(int fd, struct stat *st)
{
struct _reent r;
return esp_vfs_fstat(&r, fd, st);
}
int fcntl(int fd, int cmd, ...)
{
struct _reent r;
va_list list;
va_start(list, cmd);
int args = va_arg(list, int);
va_end(list);
return esp_vfs_fcntl_r(&r, fd, cmd, args);
}
int ioctl(int fd, unsigned long cmd, ...)
{
va_list args;
va_start(args, cmd);
int ret = esp_vfs_ioctl(fd, cmd, args);
va_end(args);
return ret;
}
int fsync(int fd)
{
return esp_vfs_fsync(fd);
}
#ifdef CONFIG_VFS_SUPPORT_SELECT
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *errorfds, struct timeval *timeout)
{
return esp_vfs_select(nfds, readfds, writefds, errorfds, timeout);
}
#endif // CONFIG_VFS_SUPPORT_SELECT
#ifdef CONFIG_VFS_SUPPORT_DIR
int stat(const char * path, struct stat * st)
{
@@ -187,3 +169,470 @@ void rewinddir(DIR* pdir)
}
#endif // CONFIG_VFS_SUPPORT_DIR
ssize_t read(int fd, void *buf, size_t count)
{
struct _reent r;
return esp_vfs_read(&r, fd, buf, count);
}
ssize_t write(int fd, const void *buf, size_t count)
{
struct _reent r;
return esp_vfs_write(&r, fd, buf, count);
}
ssize_t pread(int fd, void *buf, size_t count, off_t offset)
{
return esp_vfs_pread(fd, buf, count, offset);
}
ssize_t pwrite(int fd, const void *buf, size_t count, off_t offset)
{
return esp_vfs_pwrite(fd, buf, count, offset);
}
int open(const char *path, int flags, ...)
{
int mode = 0;
va_list ap;
va_start(ap, flags);
mode = va_arg(ap, int);
va_end(ap);
struct _reent r;
return esp_vfs_open(&r, path, flags, mode);
}
int close(int fd)
{
struct _reent r;
return esp_vfs_close(&r, fd);
}
int fcntl(int fd, int cmd, ...)
{
int arg = 0;
if (cmd != F_GETFL) {
va_list list;
va_start(list, cmd);
arg = va_arg(list, int);
va_end(list);
}
struct _reent r;
return esp_vfs_fcntl_r(&r, fd, cmd, arg);
}
off_t lseek(int fd, off_t size, int mode)
{
struct _reent r;
return esp_vfs_lseek(&r, fd, size, mode);
}
int fstat(int fd, struct stat *st)
{
struct _reent r;
return esp_vfs_fstat(&r, fd, st);
}
int ioctl(int fd, signed long cmd, ...)
{
va_list args;
va_start(args, cmd);
int ret = esp_vfs_ioctl(fd, cmd, args);
va_end(args);
return ret;
}
int fsync(int fd)
{
return esp_vfs_fsync(fd);
}
int pipe(int fds[2])
{
int ret = freertos_linux_coop_pipe(fds);
if (ret != 0) {
return ret;
}
if (s_linux_host_vfs_id >= 0) {
int vfs_fd0, vfs_fd1;
esp_err_t e0 = esp_vfs_register_fd_with_local_fd(s_linux_host_vfs_id, fds[0], false, &vfs_fd0);
esp_err_t e1 = esp_vfs_register_fd_with_local_fd(s_linux_host_vfs_id, fds[1], false, &vfs_fd1);
if (e0 != ESP_OK || e1 != ESP_OK) {
freertos_linux_coop_close(fds[0]);
freertos_linux_coop_close(fds[1]);
errno = ENFILE;
return -1;
}
fds[0] = vfs_fd0;
fds[1] = vfs_fd1;
}
return 0;
}
int pipe2(int fds[2], int flags)
{
int ret = freertos_linux_coop_pipe2(fds, flags);
if (ret != 0) {
return ret;
}
if (s_linux_host_vfs_id >= 0) {
int vfs_fd0, vfs_fd1;
esp_err_t e0 = esp_vfs_register_fd_with_local_fd(s_linux_host_vfs_id, fds[0], false, &vfs_fd0);
esp_err_t e1 = esp_vfs_register_fd_with_local_fd(s_linux_host_vfs_id, fds[1], false, &vfs_fd1);
if (e0 != ESP_OK || e1 != ESP_OK) {
freertos_linux_coop_close(fds[0]);
freertos_linux_coop_close(fds[1]);
errno = ENFILE;
return -1;
}
fds[0] = vfs_fd0;
fds[1] = vfs_fd1;
}
return 0;
}
int socket(int domain, int type, int protocol)
{
int kernel_fd = freertos_linux_coop_socket(domain, type, protocol);
return register_kernel_fd(kernel_fd);
}
int socketpair(int domain, int type, int protocol, int sv[2])
{
int ret = freertos_linux_coop_socketpair(domain, type, protocol, sv);
if (ret != 0) {
return ret;
}
if (s_linux_host_vfs_id >= 0) {
int vfs_fd0, vfs_fd1;
esp_err_t e0 = esp_vfs_register_fd_with_local_fd(s_linux_host_vfs_id, sv[0], false, &vfs_fd0);
esp_err_t e1 = esp_vfs_register_fd_with_local_fd(s_linux_host_vfs_id, sv[1], false, &vfs_fd1);
if (e0 != ESP_OK || e1 != ESP_OK) {
freertos_linux_coop_close(sv[0]);
freertos_linux_coop_close(sv[1]);
errno = ENFILE;
return -1;
}
sv[0] = vfs_fd0;
sv[1] = vfs_fd1;
}
return 0;
}
int dup(int oldfd)
{
int kernel_old = vfs_fd_to_kernel_fd(oldfd);
if (kernel_old < 0) {
return -1;
}
int kernel_fd = freertos_linux_coop_dup(kernel_old);
return register_kernel_fd(kernel_fd);
}
int dup2(int oldfd, int newfd)
{
int kernel_old = vfs_fd_to_kernel_fd(oldfd);
if (kernel_old < 0) {
return -1;
}
/* If newfd is a registered VFS FD, we need its kernel FD and must unregister it */
int kernel_new = newfd;
if (get_vfs_for_fd(newfd) != NULL) {
kernel_new = vfs_fd_to_kernel_fd(newfd);
if (kernel_new < 0) {
return -1;
}
unregister_fd(newfd);
}
int kernel_fd = freertos_linux_coop_dup2(kernel_old, kernel_new);
return register_kernel_fd(kernel_fd);
}
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
{
int kernel_sock = vfs_fd_to_kernel_fd(sockfd);
if (kernel_sock < 0) {
return -1;
}
int kernel_fd = freertos_linux_coop_accept(kernel_sock, addr, addrlen);
return register_kernel_fd(kernel_fd);
}
ssize_t readv(int fd, const struct iovec *iov, int iovcnt)
{
int kfd = vfs_fd_to_kernel_fd(fd);
if (kfd < 0) {
return -1;
}
return freertos_linux_coop_readv(kfd, iov, iovcnt);
}
ssize_t writev(int fd, const struct iovec *iov, int iovcnt)
{
int kfd = vfs_fd_to_kernel_fd(fd);
if (kfd < 0) {
return -1;
}
return freertos_linux_coop_writev(kfd, iov, iovcnt);
}
ssize_t recv(int sockfd, void *buf, size_t len, int flags)
{
int kfd = vfs_fd_to_kernel_fd(sockfd);
if (kfd < 0) {
return -1;
}
return freertos_linux_coop_recv(kfd, buf, len, flags);
}
ssize_t send(int sockfd, const void *buf, size_t len, int flags)
{
int kfd = vfs_fd_to_kernel_fd(sockfd);
if (kfd < 0) {
return -1;
}
return freertos_linux_coop_send(kfd, buf, len, flags);
}
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen)
{
int kfd = vfs_fd_to_kernel_fd(sockfd);
if (kfd < 0) {
return -1;
}
return freertos_linux_coop_recvfrom(kfd, buf, len, flags, src_addr, addrlen);
}
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen)
{
int kfd = vfs_fd_to_kernel_fd(sockfd);
if (kfd < 0) {
return -1;
}
return freertos_linux_coop_sendto(kfd, buf, len, flags, dest_addr, addrlen);
}
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags)
{
int kfd = vfs_fd_to_kernel_fd(sockfd);
if (kfd < 0) {
return -1;
}
return freertos_linux_coop_recvmsg(kfd, msg, flags);
}
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags)
{
int kfd = vfs_fd_to_kernel_fd(sockfd);
if (kfd < 0) {
return -1;
}
return freertos_linux_coop_sendmsg(kfd, msg, flags);
}
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
{
int kfd = vfs_fd_to_kernel_fd(sockfd);
if (kfd < 0) {
return -1;
}
return freertos_linux_coop_connect(kfd, addr, addrlen);
}
/**
* Translate VFS fd_sets to kernel fd_sets.
* Returns the nfds value for the kernel sets, or -1 on error (errno set).
*/
static int fdsets_vfs_to_kernel(int nfds,
const fd_set *readfds, const fd_set *writefds, const fd_set *exceptfds,
fd_set *k_readfds, fd_set *k_writefds, fd_set *k_exceptfds)
{
int max_kfd = 0;
FD_ZERO(k_readfds);
FD_ZERO(k_writefds);
FD_ZERO(k_exceptfds);
for (int fd = 0; fd < nfds; ++fd) {
int kfd = -1;
bool active = false;
if (readfds && FD_ISSET(fd, readfds)) {
kfd = vfs_fd_to_kernel_fd(fd);
if (kfd < 0) { return -1; }
FD_SET(kfd, k_readfds);
active = true;
}
if (writefds && FD_ISSET(fd, writefds)) {
if (kfd < 0) { kfd = vfs_fd_to_kernel_fd(fd); }
if (kfd < 0) { return -1; }
FD_SET(kfd, k_writefds);
active = true;
}
if (exceptfds && FD_ISSET(fd, exceptfds)) {
if (kfd < 0) { kfd = vfs_fd_to_kernel_fd(fd); }
if (kfd < 0) { return -1; }
FD_SET(kfd, k_exceptfds);
active = true;
}
if (active && kfd >= max_kfd) {
max_kfd = kfd + 1;
}
}
return max_kfd;
}
/**
* Translate kernel fd_set results back to VFS fd_sets.
*/
static void fdsets_kernel_to_vfs(int nfds,
fd_set *readfds, fd_set *writefds, fd_set *exceptfds,
fd_set *k_readfds, fd_set *k_writefds, fd_set *k_exceptfds)
{
if (readfds) { FD_ZERO(readfds); }
if (writefds) { FD_ZERO(writefds); }
if (exceptfds) { FD_ZERO(exceptfds); }
for (int fd = 0; fd < nfds; ++fd) {
int kfd = vfs_fd_to_kernel_fd(fd);
if (kfd < 0) { continue; }
if (readfds && FD_ISSET(kfd, k_readfds)) { FD_SET(fd, readfds); }
if (writefds && FD_ISSET(kfd, k_writefds)) { FD_SET(fd, writefds); }
if (exceptfds && FD_ISSET(kfd, k_exceptfds)) { FD_SET(fd, exceptfds); }
}
}
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout)
{
#ifdef CONFIG_VFS_SUPPORT_SELECT
for (int fd = 0; fd < nfds; ++fd) {
if ((readfds && FD_ISSET(fd, readfds)) ||
(writefds && FD_ISSET(fd, writefds)) ||
(exceptfds && FD_ISSET(fd, exceptfds))) {
const vfs_entry_t *vfs = get_vfs_for_fd(fd);
if (vfs != NULL && vfs->offset != s_linux_host_vfs_id) {
return esp_vfs_select(nfds, readfds, writefds, exceptfds, timeout);
}
}
}
#endif
fd_set k_readfds, k_writefds, k_exceptfds;
int max_kfd = fdsets_vfs_to_kernel(nfds, readfds, writefds, exceptfds,
&k_readfds, &k_writefds, &k_exceptfds);
if (max_kfd < 0) { return -1; }
int ret = freertos_linux_coop_select(max_kfd,
readfds ? &k_readfds : NULL,
writefds ? &k_writefds : NULL,
exceptfds ? &k_exceptfds : NULL,
timeout);
if (ret >= 0) {
fdsets_kernel_to_vfs(nfds, readfds, writefds, exceptfds,
&k_readfds, &k_writefds, &k_exceptfds);
}
return ret;
}
int pselect(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, const struct timespec *timeout,
const sigset_t *sigmask)
{
fd_set k_readfds, k_writefds, k_exceptfds;
int max_kfd = fdsets_vfs_to_kernel(nfds, readfds, writefds, exceptfds,
&k_readfds, &k_writefds, &k_exceptfds);
if (max_kfd < 0) { return -1; }
int ret = freertos_linux_coop_pselect(max_kfd,
readfds ? &k_readfds : NULL,
writefds ? &k_writefds : NULL,
exceptfds ? &k_exceptfds : NULL,
timeout, sigmask);
if (ret >= 0) {
fdsets_kernel_to_vfs(nfds, readfds, writefds, exceptfds,
&k_readfds, &k_writefds, &k_exceptfds);
}
return ret;
}
int poll(struct pollfd *fds, nfds_t nfds, int timeout)
{
struct timeval tv = {
.tv_sec = timeout / 1000,
.tv_usec = (timeout % 1000) * 1000,
};
int max_fd = -1;
fd_set readfds;
fd_set writefds;
fd_set errorfds;
if (fds == NULL) {
errno = ENOENT;
return -1;
}
FD_ZERO(&readfds);
FD_ZERO(&writefds);
FD_ZERO(&errorfds);
for (unsigned int i = 0; i < nfds; ++i) {
fds[i].revents = 0;
if (fds[i].fd < 0) {
continue;
}
if (fds[i].fd >= FD_SETSIZE) {
fds[i].revents |= POLLNVAL;
continue;
}
if (fds[i].events & (POLLIN | POLLRDNORM | POLLRDBAND | POLLPRI)) {
FD_SET(fds[i].fd, &readfds);
FD_SET(fds[i].fd, &errorfds);
max_fd = MAX(max_fd, fds[i].fd);
}
if (fds[i].events & (POLLOUT | POLLWRNORM | POLLWRBAND)) {
FD_SET(fds[i].fd, &writefds);
FD_SET(fds[i].fd, &errorfds);
max_fd = MAX(max_fd, fds[i].fd);
}
}
const int select_ret = select(max_fd + 1, &readfds, &writefds, &errorfds, timeout < 0 ? NULL : &tv);
if (select_ret < 0) {
return select_ret;
}
if (select_ret > 0) {
for (unsigned int i = 0; i < nfds; ++i) {
if (fds[i].fd < 0 || fds[i].fd >= FD_SETSIZE) {
continue;
}
if (FD_ISSET(fds[i].fd, &readfds)) {
fds[i].revents |= POLLIN;
}
if (FD_ISSET(fds[i].fd, &writefds)) {
fds[i].revents |= POLLOUT;
}
if (FD_ISSET(fds[i].fd, &errorfds)) {
fds[i].revents |= POLLERR;
}
}
}
int ret = 0;
for (unsigned int i = 0; i < nfds; ++i) {
if (fds[i].revents != 0) {
++ret;
}
}
return ret;
}
+158
View File
@@ -0,0 +1,158 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @brief Linux target VFS host filesystem registration + init
*
* Registers the cooperative libc ops (backed by freertos_linux_coop_*)
* as a VFS filesystem using esp_vfs_register_fs_with_id, so that
* file descriptors registered against this VFS ID are dispatched
* through the cooperative syscall layer.
*
* stdin/stdout/stderr are registered with VFS here, and all other
* kernel FDs are registered by the strong POSIX overrides in vfs_linux.c.
*
* Non-blocking syscalls (lseek, fstat, ioctl, fsync) are resolved directly
* via dlsym and bypass the cooperative interposition layer because they
* complete synchronously and never return EAGAIN/EWOULDBLOCK.
*/
#include <dlfcn.h>
#include <stdarg.h>
#include <unistd.h>
#include <sys/stat.h>
#include "esp_vfs.h"
#include "esp_private/freertos_linux_coop_syscalls.h"
#include "esp_private/startup_internal.h"
/* VFS ID for the Linux host filesystem — used by vfs_linux.c to register FDs */
esp_vfs_id_t s_linux_host_vfs_id = -1;
/* Real libc entry points for non-blocking syscalls, resolved at startup */
static off_t (*real_lseek)(int, off_t, int);
static int (*real_fstat)(int, struct stat *);
static int (*real_fsync)(int);
typedef int (*real_ioctl_fn_t)(int, unsigned long, ...);
static real_ioctl_fn_t real_ioctl;
static ssize_t default_read(void *ctx, int fd, void *dst, size_t size)
{
(void)ctx;
return freertos_linux_coop_read(fd, dst, size);
}
static ssize_t default_write(void *ctx, int fd, const void *data, size_t size)
{
(void)ctx;
return freertos_linux_coop_write(fd, data, size);
}
static ssize_t default_pread(void *ctx, int fd, void *dst, size_t size, off_t offset)
{
(void)ctx;
return freertos_linux_coop_pread(fd, dst, size, offset);
}
static ssize_t default_pwrite(void *ctx, int fd, const void *src, size_t size, off_t offset)
{
(void)ctx;
return freertos_linux_coop_pwrite(fd, src, size, offset);
}
static int default_open(void *ctx, const char *path, int flags, int mode)
{
(void)ctx;
return freertos_linux_coop_open(path, flags, mode);
}
static int default_close(void *ctx, int fd)
{
(void)ctx;
return freertos_linux_coop_close(fd);
}
static int default_fcntl(void *ctx, int fd, int cmd, int arg)
{
(void)ctx;
return freertos_linux_coop_fcntl(fd, cmd, arg);
}
/* lseek never blocks — call real libc directly, no cooperative retry needed */
static off_t default_lseek(void *ctx, int fd, off_t offset, int whence)
{
(void)ctx;
return real_lseek(fd, offset, whence);
}
/* fstat never blocks — call real libc directly, no cooperative retry needed */
static int default_fstat(void *ctx, int fd, struct stat *st)
{
(void)ctx;
return real_fstat(fd, st);
}
/* ioctl never blocks — call real libc directly, no cooperative retry needed */
static int default_ioctl(void *ctx, int fd, int cmd, va_list args)
{
(void)ctx;
void *arg = va_arg(args, void *);
return real_ioctl(fd, (unsigned long)cmd, arg);
}
/* fsync never blocks — call real libc directly, no cooperative retry needed */
static int default_fsync(void *ctx, int fd)
{
(void)ctx;
return real_fsync(fd);
}
static const esp_vfs_fs_ops_t s_linux_host_ops = {
.read_p = default_read,
.write_p = default_write,
.pread_p = default_pread,
.pwrite_p = default_pwrite,
.open_p = default_open,
.close_p = default_close,
.fcntl_p = default_fcntl,
.lseek_p = default_lseek,
.fstat_p = default_fstat,
.ioctl_p = default_ioctl,
.fsync_p = default_fsync,
};
ESP_SYSTEM_INIT_FN(init_vfs_linux_coop, CORE, BIT(0), 99)
{
/* Resolve real libc symbols for non-blocking syscalls */
real_lseek = dlsym(RTLD_NEXT, "lseek");
real_fstat = dlsym(RTLD_NEXT, "fstat");
real_ioctl = dlsym(RTLD_NEXT, "ioctl");
real_fsync = dlsym(RTLD_NEXT, "fsync");
freertos_linux_coop_syscalls_init();
/* Register the Linux host FS with VFS (id-based, no path prefix) */
esp_err_t err = esp_vfs_register_fs_with_id(&s_linux_host_ops,
ESP_VFS_FLAG_CONTEXT_PTR,
NULL,
&s_linux_host_vfs_id);
if (err != ESP_OK) {
return err;
}
/* Register stdin/stdout/stderr so apps see them as VFS FDs 0, 1, 2 */
int fd;
esp_vfs_register_fd_with_local_fd(s_linux_host_vfs_id, STDIN_FILENO, true, &fd);
esp_vfs_register_fd_with_local_fd(s_linux_host_vfs_id, STDOUT_FILENO, true, &fd);
esp_vfs_register_fd_with_local_fd(s_linux_host_vfs_id, STDERR_FILENO, true, &fd);
return ESP_OK;
}
void esp_vfs_include_linux_coop_init(void)
{
// Linker hook function, exists to make the linker examine this file
}