diff --git a/components/esp_system/system_init_fn.txt b/components/esp_system/system_init_fn.txt index b841acd726..1ba1dcc6dc 100644 --- a/components/esp_system/system_init_fn.txt +++ b/components/esp_system/system_init_fn.txt @@ -50,6 +50,7 @@ CORE: 105: init_newlib_time in components/esp_system/startup_funcs.c on BIT(0) CORE: 110: init_vfs_uart in components/esp_driver_uart/src/uart_vfs.c on BIT(0) CORE: 111: init_vfs_usj in components/esp_driver_usb_serial_jtag/src/usb_serial_jtag_vfs.c on BIT(0) CORE: 112: init_vfs_usj_sec in components/esp_driver_usb_serial_jtag/src/usb_serial_jtag_vfs.c on BIT(0) +CORE: 113: init_vfs_nullfs in components/vfs/nullfs.c on BIT(0) CORE: 114: init_vfs_console in components/esp_vfs_console/vfs_console.c on BIT(0) CORE: 115: init_newlib_stdio in components/newlib/newlib_init.c on BIT(0) diff --git a/components/esp_system/test_apps/console/main/test_app_main.c b/components/esp_system/test_apps/console/main/test_app_main.c index 0740cdfc50..68a0c48bed 100644 --- a/components/esp_system/test_apps/console/main/test_app_main.c +++ b/components/esp_system/test_apps/console/main/test_app_main.c @@ -4,6 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ #include +#include #include "sdkconfig.h" #include "esp_rom_uart.h" @@ -15,6 +16,9 @@ #include "driver/uart.h" #include "soc/uart_channel.h" +#include +#include + #if CONFIG_ESP_CONSOLE_NONE /* Set up UART on UART_0 (console) to be able to notify pytest test case that app booted successfully @@ -49,6 +53,15 @@ void app_main(void) { printf("Hello World\n"); + int fd = open("/dev/null", O_RDWR); + assert(fd >= 0 && "Could not open file"); // Standard check + + // Check if correct file descriptor is returned + // In this case it should be neither of 0, 1, 2 (== stdin, stdout, stderr) + assert(fd > 2 && "Incorrect file descriptor returned, stdin, stdout, stderr were not correctly assigned"); + + close(fd); + #if CONFIG_ESP_CONSOLE_NONE console_none_print(); #endif // CONFIG_ESP_CONSOLE_NONE diff --git a/components/esp_vfs_console/vfs_console.c b/components/esp_vfs_console/vfs_console.c index 980514c1d4..f6a2d26096 100644 --- a/components/esp_vfs_console/vfs_console.c +++ b/components/esp_vfs_console/vfs_console.c @@ -15,6 +15,7 @@ #include "esp_vfs_console.h" #include "sdkconfig.h" #include "esp_private/startup_internal.h" +#include "esp_vfs_null.h" #define STRINGIFY(s) STRINGIFY2(s) #define STRINGIFY2(s) #s @@ -52,6 +53,8 @@ int console_open(const char * path, int flags, int mode) vfs_console.fd_primary = open("/dev/usbserjtag", flags, mode); #elif CONFIG_ESP_CONSOLE_USB_CDC vfs_console.fd_primary = open("/dev/cdcacm", flags, mode); +#else + vfs_console.fd_primary = open("/dev/null", flags, mode); #endif // Secondary port open @@ -207,13 +210,14 @@ esp_err_t esp_vfs_console_register(void) if (err != ESP_OK) { return err; } +#else + primary_vfs = esp_vfs_null_get_vfs(); #endif // Secondary vfs part. #if CONFIG_ESP_CONSOLE_SECONDARY_USB_SERIAL_JTAG secondary_vfs = esp_vfs_usb_serial_jtag_get_vfs(); #endif - err = esp_vfs_dev_console_register(); return err; } diff --git a/components/newlib/newlib_init.c b/components/newlib/newlib_init.c index 72506d130b..cba25d3b27 100644 --- a/components/newlib/newlib_init.c +++ b/components/newlib/newlib_init.c @@ -200,7 +200,7 @@ void esp_newlib_init_global_stdio(const char *stdio_dev) ESP_SYSTEM_INIT_FN(init_newlib_stdio, CORE, BIT(0), 115) { -#if defined(CONFIG_VFS_SUPPORT_IO) && !defined(CONFIG_ESP_CONSOLE_NONE) +#if defined(CONFIG_VFS_SUPPORT_IO) esp_newlib_init_global_stdio("/dev/console"); #else esp_newlib_init_global_stdio(NULL); diff --git a/components/vfs/CMakeLists.txt b/components/vfs/CMakeLists.txt index bf884d0178..0d47c4dc97 100644 --- a/components/vfs/CMakeLists.txt +++ b/components/vfs/CMakeLists.txt @@ -7,6 +7,7 @@ endif() list(APPEND sources "vfs.c" "vfs_eventfd.c" "vfs_semihost.c" + "nullfs.c" ) list(APPEND pr esp_timer @@ -23,3 +24,6 @@ idf_component_register(SRCS ${sources} # Some newlib syscalls are implemented in vfs.c, make sure these are always # seen by the linker target_link_libraries(${COMPONENT_LIB} INTERFACE "-u vfs_include_syscalls_impl") + +# Make sure nullfs is registered +target_link_libraries(${COMPONENT_LIB} INTERFACE "-u esp_vfs_include_nullfs_register") diff --git a/components/vfs/Kconfig b/components/vfs/Kconfig index e9992e11fa..65042a8826 100644 --- a/components/vfs/Kconfig +++ b/components/vfs/Kconfig @@ -97,4 +97,11 @@ menu "Virtual file system" Define maximum number of host filesystem mount points. endmenu + config VFS_INITIALIZE_DEV_NULL + bool "Initialize /dev/null VFS" + default y + depends on VFS_SUPPORT_IO + help + If enabled, /dev/null VFS will be automatically initialized at startup. + endmenu diff --git a/components/vfs/include/esp_vfs_null.h b/components/vfs/include/esp_vfs_null.h new file mode 100644 index 0000000000..f3983894d0 --- /dev/null +++ b/components/vfs/include/esp_vfs_null.h @@ -0,0 +1,31 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include "esp_vfs.h" +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Get VFS structure for /dev/null + * + * @return VFS structure for /dev/null + */ +const esp_vfs_t *esp_vfs_null_get_vfs(void); + +/** + * @brief Register filesystem for /dev/null + * + * @return ESP_OK on success; any other value indicates an error + */ +esp_err_t esp_vfs_null_register(void); + +#ifdef __cplusplus +} +#endif diff --git a/components/vfs/nullfs.c b/components/vfs/nullfs.c new file mode 100644 index 0000000000..1324052a5d --- /dev/null +++ b/components/vfs/nullfs.c @@ -0,0 +1,323 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include "sdkconfig.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "esp_vfs.h" +#include "esp_log.h" +#include "esp_err.h" +#include "esp_private/startup_internal.h" + +#include "esp_vfs_null.h" + +#define UNUSED(x) (void)(x) +typedef enum { + VFS_NULL_CLOSED = 0, + VFS_NULL_READ = 1 << 0, + VFS_NULL_WRITE = 1 << 1, +} vfs_null_flags_t; + +typedef uint32_t vfs_null_ctx_t; +#define VFS_NULL_MAX_FDS (sizeof(vfs_null_ctx_t) * 4) + +#define GET_FLAGS(ctx, fd) ((ctx >> (fd * 2)) & 0x3) +#define SET_FLAGS(ctx, fd, flags) (ctx |= (flags << (fd * 2))) + +#define WRITABLE(ctx, fd) (GET_FLAGS(ctx, fd) & VFS_NULL_WRITE) +#define READABLE(ctx, fd) (GET_FLAGS(ctx, fd) & VFS_NULL_READ) + +#define SET_WRITABLE(ctx, fd) SET_FLAGS(ctx, fd, VFS_NULL_WRITE) +#define SET_READABLE(ctx, fd) SET_FLAGS(ctx, fd, VFS_NULL_READ) + +#define FD_IN_RANGE(fd) (fd >= 0 && fd < VFS_NULL_MAX_FDS) +#define IS_FD_VALID(fd) (FD_IN_RANGE(fd) && GET_FLAGS(g_fds, fd) != VFS_NULL_CLOSED) + +#ifdef CONFIG_ESP_CONSOLE_NONE // Prevents recursive logging +static const char* TAG = "nullfs"; +#define NULLFS_LOGD(TAG, fmt, ...) ESP_LOGD(TAG, fmt, ##__VA_ARGS__) +#else +#define NULLFS_LOGD(TAG, fmt, ...) +#endif + +static vfs_null_ctx_t g_fds = 0; + +static ssize_t vfs_null_write(int fd, const void *data, size_t size); +static off_t vfs_null_lseek(int fd, off_t offset, int whence); +static ssize_t vfs_null_read(int fd, void *data, size_t size); +static ssize_t vfs_null_pread(int fd, void *data, size_t size, off_t offset); +static ssize_t vfs_null_pwrite(int fd, const void *data, size_t size, off_t offset); +static int vfs_null_open(const char* path, int flags, int mode); +static int vfs_null_close(int fd); +static int vfs_null_fstat(int fd, struct stat *st); +#if CONFIG_VFS_SUPPORT_DIR +static int vfs_null_stat(const char* path, struct stat *st); +#endif // CONFIG_VFS_SUPPORT_DIR +static int vfs_null_fcntl(int fd, int cmd, int arg); +static int vfs_null_ioctl(int fd, int cmd, va_list args); +static int vfs_null_fsync(int fd); + +static const esp_vfs_t s_vfs_null = { + .flags = ESP_VFS_FLAG_DEFAULT, + .write = &vfs_null_write, + .lseek = &vfs_null_lseek, + .read = &vfs_null_read, + .pread = &vfs_null_pread, + .pwrite = &vfs_null_pwrite, + .open = &vfs_null_open, + .close = &vfs_null_close, + .fstat = &vfs_null_fstat, +#if CONFIG_VFS_SUPPORT_DIR + .stat = &vfs_null_stat, +#endif // CONFIG_VFS_SUPPORT_DIR + .fcntl = &vfs_null_fcntl, + .ioctl = &vfs_null_ioctl, + .fsync = &vfs_null_fsync, +}; + +const esp_vfs_t *esp_vfs_null_get_vfs(void) +{ + return &s_vfs_null; +} + +esp_err_t esp_vfs_null_register(void) +{ + return esp_vfs_register("/dev/null", &s_vfs_null, NULL); +} + +static ssize_t vfs_null_write(int fd, const void *data, size_t size) +{ + UNUSED(data); + + if (FD_IN_RANGE(fd) && WRITABLE(g_fds, fd)) { + return size; + } + + errno = EBADF; + return -1; +} + +static off_t vfs_null_lseek(int fd, off_t offset, int whence) +{ + UNUSED(offset); + + if (!IS_FD_VALID(fd)) { + errno = EBADF; + return -1; + } + + switch (whence) { + case SEEK_SET: + case SEEK_CUR: + case SEEK_END: + return 0; + default: + errno = EINVAL; + return -1; + } +} + +static ssize_t vfs_null_read(int fd, void *data, size_t size) +{ + UNUSED(data); + + NULLFS_LOGD(TAG, "read %u bytes", size); + + if (FD_IN_RANGE(fd) && READABLE(g_fds, fd)) { + return 0; // EOF + } + + errno = EBADF; + return -1; +} + +static int vfs_null_pread(int fd, void *data, size_t size, off_t offset) +{ + UNUSED(data); + UNUSED(size); + UNUSED(offset); + + NULLFS_LOGD(TAG, "pread %u bytes at offset %ld", size, offset); + + if (!FD_IN_RANGE(fd) || !READABLE(g_fds, fd)) { + errno = EBADF; + return -1; + } + + return 0; // EOF + +} + +static int vfs_null_pwrite(int fd, const void *data, size_t size, off_t offset) +{ + UNUSED(data); + UNUSED(offset); + + NULLFS_LOGD(TAG, "pwrite %u bytes at offset %ld", size, offset); + + if (!FD_IN_RANGE(fd) || !WRITABLE(g_fds, fd)) { + errno = EBADF; + return -1; + } + + return size; +} + +static int vfs_null_get_empty_fd(void) +{ + for (int i = 0; i < VFS_NULL_MAX_FDS; i++) { + if (GET_FLAGS(g_fds, i) == VFS_NULL_CLOSED) { + return i; + } + } + + return -1; +} + +static int vfs_null_open(const char* path, int flags, int mode) +{ + UNUSED(mode); + + NULLFS_LOGD(TAG, "open %s, flags %d", path, flags); + + // Possibly check if the flags are valid + + if (strcmp(path, "/") != 0) { + errno = ENOENT; + return -1; + } + + int fd = vfs_null_get_empty_fd(); + if (fd == -1) { + errno = EMFILE; + return -1; + } + + if (flags & O_RDWR) { + SET_READABLE(g_fds, fd); + SET_WRITABLE(g_fds, fd); + } else if (flags & O_WRONLY) { + SET_WRITABLE(g_fds, fd); + } else if (flags & O_RDONLY) { + SET_READABLE(g_fds, fd); + } else { + errno = EINVAL; + return -1; + } + + return fd; +} + +static int vfs_null_close(int fd) +{ + if (!FD_IN_RANGE(fd)) { + errno = EBADF; + return -1; + } + + SET_FLAGS(g_fds, fd, VFS_NULL_CLOSED); + return 0; +} + +static int vfs_null_fstat(int fd, struct stat *st) +{ + if (!FD_IN_RANGE(fd)) { + errno = EBADF; + return -1; + } + + memset(st, 0, sizeof(struct stat)); + st->st_mode = S_IFCHR | 0666; // Special character device with read/write permissions for everyone + st->st_nlink = 1; + st->st_uid = 0; + st->st_gid = 0; + st->st_size = 0; + + return 0; +} + +#if CONFIG_VFS_SUPPORT_DIR + +static int vfs_null_stat(const char* path, struct stat *st) +{ + if (strcmp(path, "/") != 0) { + errno = ENOENT; + return -1; + } + + memset(st, 0, sizeof(struct stat)); + st->st_mode = S_IFCHR | 0666; // Special character device with read/write permissions for everyone + st->st_nlink = 1; + st->st_uid = 0; + st->st_gid = 0; + st->st_size = 0; + + return 0; +} + +#endif // CONFIG_VFS_SUPPORT_DIR + +static int vfs_null_fcntl(int fd, int cmd, int arg) +{ + UNUSED(arg); + + if (!FD_IN_RANGE(fd)) { + errno = EBADF; + return -1; + } + + switch (cmd) { + default: + errno = ENOSYS; + return -1; + } +} + +static int vfs_null_ioctl(int fd, int cmd, va_list args) +{ + UNUSED(args); + + if (!FD_IN_RANGE(fd)) { + errno = EBADF; + return -1; + } + + switch (cmd) { + default: + errno = ENOSYS; + return -1; + } +} + +static int vfs_null_fsync(int fd) +{ + if (!FD_IN_RANGE(fd)) { + errno = EBADF; + return -1; + } + + return 0; +} + +#if defined(CONFIG_VFS_INITIALIZE_DEV_NULL) || defined(CONFIG_ESP_CONSOLE_NONE) +ESP_SYSTEM_INIT_FN(init_vfs_nullfs, CORE, BIT(0), 113) +{ + return esp_vfs_null_register(); +} +#endif // CONFIG_VFS_INITIALIZE_DEV_NULL + +void esp_vfs_include_nullfs_register(void) +{ + // Linker hook function, exists to make the linker examine this file +} diff --git a/components/vfs/test_apps/main/CMakeLists.txt b/components/vfs/test_apps/main/CMakeLists.txt index f9a5e774c9..7ab4968a9e 100644 --- a/components/vfs/test_apps/main/CMakeLists.txt +++ b/components/vfs/test_apps/main/CMakeLists.txt @@ -2,7 +2,7 @@ set(src "test_app_main.c" "test_vfs_access.c" "test_vfs_append.c" "test_vfs_eventfd.c" "test_vfs_fd.c" "test_vfs_lwip.c" "test_vfs_open.c" "test_vfs_paths.c" - "test_vfs_select.c" + "test_vfs_select.c" "test_vfs_nullfs.c" ) idf_component_register(SRCS ${src} diff --git a/components/vfs/test_apps/main/test_vfs_nullfs.c b/components/vfs/test_apps/main/test_vfs_nullfs.c new file mode 100644 index 0000000000..74fc95e0cd --- /dev/null +++ b/components/vfs/test_apps/main/test_vfs_nullfs.c @@ -0,0 +1,138 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +#include +#include +#include + +#include "unity.h" +#include "esp_vfs.h" + +#include "esp_vfs_null.h" +#include "unity_test_runner.h" + +TEST_CASE("Can open and close /dev/null", "[vfs_nullfs]") +{ + int fd = open("/dev/null", O_RDWR); + TEST_ASSERT(fd >= 0); + close(fd); +} + +TEST_CASE("Can write to /dev/null", "[vfs_nullfs]") +{ + int fd = open("/dev/null", O_RDWR); + TEST_ASSERT(fd >= 0); + + // Can write to /dev/null + ssize_t ret = write(fd, "hello", 5); + TEST_ASSERT_EQUAL(5, ret); + + // Write does not change the file offset + off_t offset = lseek(fd, 0, SEEK_CUR); + TEST_ASSERT_EQUAL(0, offset); + + close(fd); +} + +TEST_CASE("Can read from /dev/null", "[vfs_nullfs]") +{ + int fd = open("/dev/null", O_RDWR); + TEST_ASSERT(fd >= 0); + + // Can read from /dev/null + char buf[5] = {0}; + ssize_t ret = read(fd, buf, 5); + + // Read always returns 0 bytes -> EOF + TEST_ASSERT_EQUAL(0, ret); + + // Read does not modify the buffer + for (int i = 0; i < 5; i++) { + TEST_ASSERT_EQUAL(0, buf[i]); + } + + // Read does not change the file offset + off_t offset = lseek(fd, 0, SEEK_CUR); + TEST_ASSERT_EQUAL(0, offset); + + + close(fd); +} + +TEST_CASE("Can lseek /dev/null", "[vfs_nullfs]") +{ + int fd = open("/dev/null", O_RDWR); + TEST_ASSERT(fd >= 0); + + off_t offset = lseek(fd, 0, SEEK_SET); + TEST_ASSERT_EQUAL(0, offset); + + offset = lseek(fd, 0, SEEK_CUR); + TEST_ASSERT_EQUAL(0, offset); + + offset = lseek(fd, 0, SEEK_END); + TEST_ASSERT_EQUAL(0, offset); + + close(fd); +} + +TEST_CASE("Can fstat /dev/null", "[vfs_nullfs]") +{ + int fd = open("/dev/null", O_RDWR); + TEST_ASSERT(fd >= 0); + + struct stat st; + int ret = fstat(fd, &st); + TEST_ASSERT_EQUAL(0, ret); + + TEST_ASSERT_EQUAL(0, st.st_size); + TEST_ASSERT_EQUAL(S_IFCHR, st.st_mode & S_IFMT); + + close(fd); +} + +TEST_CASE("Can fsync /dev/null", "[vfs_nullfs]") +{ + int fd = open("/dev/null", O_RDWR); + TEST_ASSERT(fd >= 0); + + int ret = fsync(fd); + TEST_ASSERT_EQUAL(0, ret); + + close(fd); +} + +TEST_CASE("Can pread /dev/null", "[vfs_nullfs]") +{ + int fd = open("/dev/null", O_RDWR); + TEST_ASSERT(fd >= 0); + + char buf[5] = {0}; + ssize_t ret = pread(fd, buf, 5, 0); + TEST_ASSERT_EQUAL(0, ret); + + close(fd); +} + +TEST_CASE("Can pwrite /dev/null", "[vfs_nullfs]") +{ + int fd = open("/dev/null", O_RDWR); + TEST_ASSERT(fd >= 0); + + ssize_t ret = pwrite(fd, "hello", 5, 0); + TEST_ASSERT_EQUAL(5, ret); + + close(fd); +} + +TEST_CASE("Can stat /dev/null", "[vfs_nullfs]") +{ + struct stat st; + int ret = stat("/dev/null", &st); + TEST_ASSERT_EQUAL(0, ret); + + TEST_ASSERT_EQUAL(0, st.st_size); + TEST_ASSERT_EQUAL(S_IFCHR, st.st_mode & S_IFMT); +} diff --git a/docs/doxygen/Doxyfile b/docs/doxygen/Doxyfile index d54d9deb76..ebcfe85fb8 100644 --- a/docs/doxygen/Doxyfile +++ b/docs/doxygen/Doxyfile @@ -305,6 +305,7 @@ INPUT = \ $(PROJECT_PATH)/components/vfs/include/esp_vfs_dev.h \ $(PROJECT_PATH)/components/vfs/include/esp_vfs_eventfd.h \ $(PROJECT_PATH)/components/vfs/include/esp_vfs_semihost.h \ + $(PROJECT_PATH)/components/vfs/include/esp_vfs_null.h \ $(PROJECT_PATH)/components/vfs/include/esp_vfs.h \ $(PROJECT_PATH)/components/wear_levelling/include/wear_levelling.h \ $(PROJECT_PATH)/components/wifi_provisioning/include/wifi_provisioning/manager.h \ diff --git a/docs/en/api-reference/storage/vfs.rst b/docs/en/api-reference/storage/vfs.rst index c50527716d..315ea68eb2 100644 --- a/docs/en/api-reference/storage/vfs.rst +++ b/docs/en/api-reference/storage/vfs.rst @@ -238,3 +238,5 @@ API Reference .. include-build-file:: inc/uart_vfs.inc .. include-build-file:: inc/esp_vfs_eventfd.inc + +.. include-build-file:: inc/esp_vfs_null.inc diff --git a/docs/zh_CN/api-reference/storage/vfs.rst b/docs/zh_CN/api-reference/storage/vfs.rst index b27bbd3420..eabac3a765 100644 --- a/docs/zh_CN/api-reference/storage/vfs.rst +++ b/docs/zh_CN/api-reference/storage/vfs.rst @@ -238,3 +238,5 @@ API 参考 .. include-build-file:: inc/uart_vfs.inc .. include-build-file:: inc/esp_vfs_eventfd.inc + +.. include-build-file:: inc/esp_vfs_null.inc