-
Notifications
You must be signed in to change notification settings - Fork 7.6k
libc: minimal: stdin: Add getc() implementation and unit tests #93062
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
/* stdin_console.c */ | ||
|
||
/* | ||
* Copyright (c) 2025 The Zephyr Project Contributors | ||
* | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
#include <stdio.h> | ||
#include <zephyr/sys/libc-hooks.h> | ||
#include <zephyr/internal/syscall_handler.h> | ||
#include <string.h> | ||
|
||
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 <zephyr/syscalls/zephyr_fgetc_mrsh.c> | ||
#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 <zephyr/syscalls/zephyr_fread_mrsh.c> | ||
#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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should probably be moved to a separate file. |
||
{ | ||
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; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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}) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
Original file line number | Diff line number | Diff line change | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -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 <zephyr/ztest.h> | ||||||||||||
#include <stdio.h> | ||||||||||||
#include <string.h> | ||||||||||||
|
||||||||||||
static const char *test_input = "Hello\nWorld"; | ||||||||||||
static int input_pos; | ||||||||||||
|
||||||||||||
static int mock_stdin_hook(void) | ||||||||||||
{ | ||||||||||||
if (test_input[input_pos] == '\0') { | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. With the suggested change, It also allows you to test how the implementation reacts to processing Maybe even put some additional text after the
Suggested change
|
||||||||||||
return EOF; | ||||||||||||
} | ||||||||||||
return test_input[input_pos++]; | ||||||||||||
} | ||||||||||||
|
||||||||||||
void setup_stdin_hook(void) | ||||||||||||
{ | ||||||||||||
Comment on lines
+26
to
+27
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This can be a "before" callback provided to
Suggested change
|
||||||||||||
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'"); | ||||||||||||
Comment on lines
+36
to
+39
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not add a loop and check the entirety of the input? |
||||||||||||
} | ||||||||||||
|
||||||||||||
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'"); | ||||||||||||
Comment on lines
+45
to
+48
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not add a loop and check the entirety of the input? |
||||||||||||
} | ||||||||||||
|
||||||||||||
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'"); | ||||||||||||
Comment on lines
+54
to
+57
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not add a loop and check the entirety of the input? |
||||||||||||
} | ||||||||||||
|
||||||||||||
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); | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Additionally, pass in the "before" function to be called automatically by the test suite like so.
Suggested change
The one thing I would consider potentially is also creating a way to back up the previous stdin hook if one does not exist, and to restore it in a "after" callback. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These should probably be moved to separate files