From fc79a8c703e05a1c86218bc773f11e99c895b639 Mon Sep 17 00:00:00 2001 From: Seyoung Jeong Date: Fri, 11 Jul 2025 15:47:40 -0700 Subject: [PATCH] libc: minimal: stdin: Add getc() implementation and unit tests - Implement getc() in lib/libc/minimal/source/stdin/stdin_console.c. - Add unit tests for getc(), fgetc(), fgets(), and getchar() in tests/lib/sscanf using a mock stdin hook. - Update headers and CMakeLists for new input support. Resolves basic input API coverage for issue #66943. Signed-off-by: Seyoung Jeong --- include/zephyr/sys/libc-hooks.h | 2 + lib/libc/minimal/CMakeLists.txt | 1 + lib/libc/minimal/include/stdio.h | 3 + lib/libc/minimal/source/stdin/stdin_console.c | 151 ++++++++++++++++++ tests/lib/sscanf/CMakeLists.txt | 8 + tests/lib/sscanf/prj.conf | 6 + tests/lib/sscanf/src/main.c | 72 +++++++++ 7 files changed, 243 insertions(+) create mode 100644 lib/libc/minimal/source/stdin/stdin_console.c create mode 100644 tests/lib/sscanf/CMakeLists.txt create mode 100644 tests/lib/sscanf/prj.conf create mode 100644 tests/lib/sscanf/src/main.c diff --git a/include/zephyr/sys/libc-hooks.h b/include/zephyr/sys/libc-hooks.h index 1b420674149f..ede1f0e6c803 100644 --- a/include/zephyr/sys/libc-hooks.h +++ b/include/zephyr/sys/libc-hooks.h @@ -35,6 +35,8 @@ __syscall int zephyr_write_stdout(const void *buf, int nbytes); __syscall int zephyr_fputc(int c, FILE * stream); +__syscall int zephyr_fgetc(FILE *stream); + #ifdef CONFIG_MINIMAL_LIBC /* Minimal libc only */ diff --git a/lib/libc/minimal/CMakeLists.txt b/lib/libc/minimal/CMakeLists.txt index 29f2db74367b..03741723020f 100644 --- a/lib/libc/minimal/CMakeLists.txt +++ b/lib/libc/minimal/CMakeLists.txt @@ -28,6 +28,7 @@ zephyr_library_sources( source/string/strstr.c source/string/string.c source/string/strspn.c + source/stdin/stdin_console.c source/stdout/stdout_console.c source/stdout/sprintf.c source/stdout/fprintf.c diff --git a/lib/libc/minimal/include/stdio.h b/lib/libc/minimal/include/stdio.h index 12b53ae81b27..dbe7006b5d24 100644 --- a/lib/libc/minimal/include/stdio.h +++ b/lib/libc/minimal/include/stdio.h @@ -64,6 +64,9 @@ int remove(const char *path); #define putc(c, stream) fputc(c, stream) #define putchar(c) putc(c, stdout) +int fgetc(FILE *stream); +#define getc(stream) fgetc(stream) + #ifdef __cplusplus } #endif diff --git a/lib/libc/minimal/source/stdin/stdin_console.c b/lib/libc/minimal/source/stdin/stdin_console.c new file mode 100644 index 000000000000..563ffee3fc2f --- /dev/null +++ b/lib/libc/minimal/source/stdin/stdin_console.c @@ -0,0 +1,151 @@ +/* stdin_console.c */ + +/* + * Copyright (c) 2025 The Zephyr Project Contributors + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include + +static unsigned char _stdin_hook_default(void) +{ + return 0; +} + +static unsigned char (*_stdin_hook)(void) = _stdin_hook_default; + +void __stdin_hook_install(unsigned char (*hook)(void)) +{ + _stdin_hook = hook; +} + +int z_impl_zephyr_fgetc(FILE *stream) +{ + if (stream == stdin && _stdin_hook) { + return _stdin_hook(); + } + return EOF; +} + +#ifdef CONFIG_USERSPACE +static inline int z_vrfy_zephyr_fgetc(FILE *stream) +{ + return z_impl_zephyr_fgetc(stream); +} +#include +#endif + +int fgetc(FILE *stream) +{ + return zephyr_fgetc(stream); +} + +char *fgets(char *s, int size, FILE *stream) +{ + if (s == NULL || size <= 0) { + return NULL; + } + + int i = 0; + int c; + + while (i < size - 1) { + c = fgetc(stream); + if (c == EOF) { + if (i == 0) { + return NULL; + } + break; + } + s[i++] = (char)c; + if (c == '\n') { + break; + } + } + s[i] = '\0'; + return s; +} + +#undef getc +int getc(FILE *stream) +{ + return zephyr_fgetc(stream); +} + +#undef getchar +int getchar(void) +{ + return zephyr_fgetc(stdin); +} + +size_t z_impl_zephyr_fread(void *ZRESTRICT ptr, size_t size, size_t nitems, FILE *ZRESTRICT stream) +{ + size_t i, j; + unsigned char *p = ptr; + + if ((stream != stdin) || (nitems == 0) || (size == 0)) { + return 0; + } + + i = nitems; + do { + j = size; + do { + int c = fgetc(stream); + if (c == EOF) { + goto done; + } + *p++ = (unsigned char)c; + j--; + } while (j > 0); + + i--; + } while (i > 0); + +done: + return (nitems - i); +} + +#ifdef CONFIG_USERSPACE +static inline size_t z_vrfy_zephyr_fread(void *ZRESTRICT ptr, size_t size, size_t nitems, + FILE *ZRESTRICT stream) +{ + K_OOPS(K_SYSCALL_MEMORY_ARRAY_WRITE(ptr, nitems, size)); + return z_impl_zephyr_fread(ptr, size, nitems, stream); +} +#include +#endif + +size_t fread(void *ZRESTRICT ptr, size_t size, size_t nitems, FILE *ZRESTRICT stream) +{ + return zephyr_fread(ptr, size, nitems, stream); +} + +char *gets(char *s) +{ + if (s == NULL) { + return NULL; + } + + int c; + char *p = s; + + while (1) { + c = getchar(); + if (c == EOF || c == '\n') { + break; + } + *p++ = (char)c; + } + *p = '\0'; + + // If nothing was read and EOF, return NULL + if (p == s && c == EOF) { + return NULL; + } + return s; +} diff --git a/tests/lib/sscanf/CMakeLists.txt b/tests/lib/sscanf/CMakeLists.txt new file mode 100644 index 000000000000..4b5f01dbf200 --- /dev/null +++ b/tests/lib/sscanf/CMakeLists.txt @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(sscanf) + +FILE(GLOB app_sources src/*.c) +target_sources(app PRIVATE ${app_sources}) diff --git a/tests/lib/sscanf/prj.conf b/tests/lib/sscanf/prj.conf new file mode 100644 index 000000000000..084dd06744fd --- /dev/null +++ b/tests/lib/sscanf/prj.conf @@ -0,0 +1,6 @@ +CONFIG_ZTEST=y +CONFIG_FPU=y +CONFIG_TEST_USERSPACE=y +CONFIG_ZTEST_FATAL_HOOK=y +CONFIG_PICOLIBC_IO_FLOAT=y +CONFIG_ZTEST_STACK_SIZE=2048 diff --git a/tests/lib/sscanf/src/main.c b/tests/lib/sscanf/src/main.c new file mode 100644 index 000000000000..80c2092a3ceb --- /dev/null +++ b/tests/lib/sscanf/src/main.c @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2025 The Zephyr Project Contributors + * + * SPDX-License-Identifier: Apache-2.0 + * + * DESCRIPTION + * This module contains the code for testing input functionality in minimal libc, + * including getc(), fgetc(), fgets(), and getchar(). + */ + +#include +#include +#include + +static const char *test_input = "Hello\nWorld"; +static int input_pos; + +static int mock_stdin_hook(void) +{ + if (test_input[input_pos] == '\0') { + return EOF; + } + return test_input[input_pos++]; +} + +void setup_stdin_hook(void) +{ + input_pos = 0; + extern void __stdin_hook_install(int (*hook)(void)); + __stdin_hook_install(mock_stdin_hook); +} + +ZTEST(sscanf, test_getc) +{ + setup_stdin_hook(); + int c = getc(stdin); + zassert_equal(c, 'H', "getc(stdin) did not return 'H'"); + c = getc(stdin); + zassert_equal(c, 'e', "getc(stdin) did not return 'e'"); +} + +ZTEST(sscanf, test_fgetc) +{ + setup_stdin_hook(); + int c = fgetc(stdin); + zassert_equal(c, 'H', "fgetc(stdin) did not return 'H'"); + c = fgetc(stdin); + zassert_equal(c, 'e', "fgetc(stdin) did not return 'e'"); +} + +ZTEST(sscanf, test_getchar) +{ + setup_stdin_hook(); + int c = getchar(); + zassert_equal(c, 'H', "getchar() did not return 'H'"); + c = getchar(); + zassert_equal(c, 'e', "getchar() did not return 'e'"); +} + +ZTEST(sscanf, test_fgets) +{ + setup_stdin_hook(); + char buf[16]; + char *ret = fgets(buf, sizeof(buf), stdin); + zassert_not_null(ret, "fgets returned NULL"); + zassert_true(strcmp(buf, "Hello\n") == 0, "fgets did not read 'Hello\\n'"); + ret = fgets(buf, sizeof(buf), stdin); + zassert_not_null(ret, "fgets returned NULL on second call"); + zassert_true(strcmp(buf, "World") == 0, "fgets did not read 'World'"); +} + +ZTEST_SUITE(sscanf, NULL, NULL, NULL, NULL, NULL);