[PATCH 13/15] tools/nolibc: add fopen()

Thomas Weißschuh posted 15 patches 7 months, 3 weeks ago
There is a newer version of this series
[PATCH 13/15] tools/nolibc: add fopen()
Posted by Thomas Weißschuh 7 months, 3 weeks ago
This is used in various selftests and will be handy when integrating
those with nolibc.

Only the standard POSIX modes are supported.
No extensions nor the (noop) "b" from ISO C are accepted.

Signed-off-by: Thomas Weißschuh <thomas.weissschuh@linutronix.de>
---
 tools/include/nolibc/stdio.h                 | 27 +++++++++++++++++++++++++++
 tools/testing/selftests/nolibc/nolibc-test.c | 24 ++++++++++++++++++++++++
 2 files changed, 51 insertions(+)

diff --git a/tools/include/nolibc/stdio.h b/tools/include/nolibc/stdio.h
index 02426682c34bcd82d9556036c6c3e73a6a5a3b4d..86a1132abcd4e5e5b0f821cfe974aca8a6d4966f 100644
--- a/tools/include/nolibc/stdio.h
+++ b/tools/include/nolibc/stdio.h
@@ -10,6 +10,7 @@
 #include "std.h"
 #include "arch.h"
 #include "errno.h"
+#include "fcntl.h"
 #include "types.h"
 #include "sys.h"
 #include "stdarg.h"
@@ -52,6 +53,32 @@ FILE *fdopen(int fd, const char *mode __attribute__((unused)))
 	return (FILE*)(intptr_t)~fd;
 }
 
+static __attribute__((unused))
+FILE *fopen(const char *pathname, const char *mode)
+{
+	int flags, fd;
+
+	if (!strcmp(mode, "r"))
+		flags = O_RDONLY;
+	else if (!strcmp(mode, "w"))
+		flags = O_WRONLY | O_CREAT | O_TRUNC;
+	else if (!strcmp(mode, "a"))
+		flags = O_WRONLY | O_CREAT | O_APPEND;
+	else if (!strcmp(mode, "r+"))
+		flags = O_RDWR;
+	else if (!strcmp(mode, "w+"))
+		flags = O_RDWR | O_CREAT | O_TRUNC;
+	else if (!strcmp(mode, "a+"))
+		flags = O_RDWR | O_CREAT | O_APPEND;
+	else {
+		SET_ERRNO(EINVAL);
+		return NULL;
+	}
+
+	fd = open(pathname, flags, 0666);
+	return fdopen(fd, mode);
+}
+
 /* provides the fd of stream. */
 static __attribute__((unused))
 int fileno(FILE *stream)
diff --git a/tools/testing/selftests/nolibc/nolibc-test.c b/tools/testing/selftests/nolibc/nolibc-test.c
index ab9c3bcffd9750981d68c6d16245d285ce0657c8..f576edc3725b42ce086d6d83d989f38d3fab6eb8 100644
--- a/tools/testing/selftests/nolibc/nolibc-test.c
+++ b/tools/testing/selftests/nolibc/nolibc-test.c
@@ -856,6 +856,29 @@ int test_getpagesize(void)
 	return !c;
 }
 
+int test_file_stream(void)
+{
+	FILE *f;
+	int r;
+
+	f = fopen("/dev/null", "r");
+	if (!f)
+		return -1;
+
+	errno = 0;
+	r = fwrite("foo", 1, 3, f);
+	if (r != 0 || errno != EBADF) {
+		fclose(f);
+		return -1;
+	}
+
+	r = fclose(f);
+	if (r == EOF)
+		return -1;
+
+	return 0;
+}
+
 int test_fork(void)
 {
 	int status;
@@ -1308,6 +1331,7 @@ int run_syscall(int min, int max)
 		CASE_TEST(dup3_0);            tmp = dup3(0, 100, 0);  EXPECT_SYSNE(1, tmp, -1); close(tmp); break;
 		CASE_TEST(dup3_m1);           tmp = dup3(-1, 100, 0); EXPECT_SYSER(1, tmp, -1, EBADF); if (tmp != -1) close(tmp); break;
 		CASE_TEST(execve_root);       EXPECT_SYSER(1, execve("/", (char*[]){ [0] = "/", [1] = NULL }, NULL), -1, EACCES); break;
+		CASE_TEST(file_stream);       EXPECT_SYSZR(1, test_file_stream()); break;
 		CASE_TEST(fork);              EXPECT_SYSZR(1, test_fork()); break;
 		CASE_TEST(getdents64_root);   EXPECT_SYSNE(1, test_getdents64("/"), -1); break;
 		CASE_TEST(getdents64_null);   EXPECT_SYSER(1, test_getdents64("/dev/null"), -1, ENOTDIR); break;

-- 
2.49.0

Re: [PATCH 13/15] tools/nolibc: add fopen()
Posted by Willy Tarreau 7 months, 3 weeks ago
On Wed, Apr 23, 2025 at 05:01:43PM +0200, Thomas Weißschuh wrote:
> +static __attribute__((unused))
> +FILE *fopen(const char *pathname, const char *mode)
> +{
> +	int flags, fd;
> +
> +	if (!strcmp(mode, "r"))
> +		flags = O_RDONLY;
> +	else if (!strcmp(mode, "w"))
> +		flags = O_WRONLY | O_CREAT | O_TRUNC;
> +	else if (!strcmp(mode, "a"))
> +		flags = O_WRONLY | O_CREAT | O_APPEND;
> +	else if (!strcmp(mode, "r+"))
> +		flags = O_RDWR;
> +	else if (!strcmp(mode, "w+"))
> +		flags = O_RDWR | O_CREAT | O_TRUNC;
> +	else if (!strcmp(mode, "a+"))
> +		flags = O_RDWR | O_CREAT | O_APPEND;
> +	else {
> +		SET_ERRNO(EINVAL);
> +		return NULL;
> +	}

I'm concerned by the size of the function due to the repeated strcmp()
calls (also I find strcmp()==0 more readable that !strcmp() which I tend
to read as "not string compares").

I have not tried the code below but I think it could cover it in a maybe
lighter way:

	switch (*mode) {
		case 'r": flags = O_RDONLY; break;
		case 'w': flags = O_WRONLY | O_CREAT | O_TRUNC; break;
		case 'a': flags = O_WRONLY | O_CREAT | O_APPEND; break;
        	default : SET_ERRNO(EINVAL); return NULL;
        }

	if (mode[1] == '+')
		flags = (flags & ~(O_RDONLY|O_WRONLY)) | O_RDWR;

I think it does the same but should be significantly lighter.

Willy