From: Thomas Weißschuh <thomas.weisssc...@linutronix.de>

Add an implementation for directory access operations.
To keep nolibc itself allocation-free, a "DIR *" does not point to any
data, but directly encodes a filedescriptor number, equivalent to "FILE *".
Without any per-directory storage it is not possible to implement
readdir() POSIX confirming. Instead only readdir_r() is provided.
While readdir_r() is deprecated in glibc, the reasons for that are
not applicable to nolibc.

Signed-off-by: Thomas Weißschuh <thomas.weisssc...@linutronix.de>
Signed-off-by: Thomas Weißschuh <li...@weissschuh.net>
---
 tools/include/nolibc/Makefile                |  1 +
 tools/include/nolibc/dirent.h                | 98 ++++++++++++++++++++++++++++
 tools/include/nolibc/nolibc.h                |  1 +
 tools/testing/selftests/nolibc/nolibc-test.c | 39 +++++++++++
 4 files changed, 139 insertions(+)

diff --git a/tools/include/nolibc/Makefile b/tools/include/nolibc/Makefile
index 
a1f55fb24bb38c1f49c653af5825e8bcc569a56d..dceec0e1a135119108d6f4dcb3d2ec57c002ffd3
 100644
--- a/tools/include/nolibc/Makefile
+++ b/tools/include/nolibc/Makefile
@@ -29,6 +29,7 @@ all_files := \
                compiler.h \
                crt.h \
                ctype.h \
+               dirent.h \
                errno.h \
                nolibc.h \
                signal.h \
diff --git a/tools/include/nolibc/dirent.h b/tools/include/nolibc/dirent.h
new file mode 100644
index 
0000000000000000000000000000000000000000..c5c30d0dd6806b1bec2fa8120a3df29aaa201393
--- /dev/null
+++ b/tools/include/nolibc/dirent.h
@@ -0,0 +1,98 @@
+/* SPDX-License-Identifier: LGPL-2.1 OR MIT */
+/*
+ * Directory access for NOLIBC
+ * Copyright (C) 2025 Thomas Weißschuh <li...@weissschuh.net>
+ */
+
+#ifndef _NOLIBC_DIRENT_H
+#define _NOLIBC_DIRENT_H
+
+#include "stdint.h"
+#include "types.h"
+
+#include <linux/limits.h>
+
+struct dirent {
+       ino_t   d_ino;
+       char    d_name[NAME_MAX + 1];
+};
+
+/* See comment of FILE in stdio.h */
+typedef struct {
+       char dummy[1];
+} DIR;
+
+static __attribute__((unused))
+DIR *fdopendir(int fd)
+{
+       if (fd < 0) {
+               SET_ERRNO(EBADF);
+               return NULL;
+       }
+       return (DIR *)(intptr_t)~fd;
+}
+
+static __attribute__((unused))
+DIR *opendir(const char *name)
+{
+       int fd;
+
+       fd = open(name, O_RDONLY);
+       if (fd == -1)
+               return NULL;
+       return fdopendir(fd);
+}
+
+static __attribute__((unused))
+int closedir(DIR *dirp)
+{
+       intptr_t i = (intptr_t)dirp;
+
+       if (i >= 0) {
+               SET_ERRNO(EBADF);
+               return -1;
+       }
+       return close(~i);
+}
+
+static __attribute__((unused))
+int readdir_r(DIR *dirp, struct dirent *entry, struct dirent **result)
+{
+       char buf[sizeof(struct linux_dirent64) + NAME_MAX + 1];
+       struct linux_dirent64 *ldir = (void *)buf;
+       intptr_t i = (intptr_t)dirp;
+       int fd, ret;
+
+       if (i >= 0)
+               return EBADF;
+
+       fd = ~i;
+
+       ret = sys_getdents64(fd, ldir, sizeof(buf));
+       if (ret < 0)
+               return -ret;
+       if (ret == 0) {
+               *result = NULL;
+               return 0;
+       }
+
+       /*
+        * getdents64() returns as many entries as fit the buffer.
+        * readdir() can only return one entry at a time.
+        * Make sure the non-returned ones are not skipped.
+        */
+       ret = lseek(fd, ldir->d_off, SEEK_SET);
+       if (ret == -1)
+               return errno;
+
+       entry->d_ino = ldir->d_ino;
+       /* the destination should always be big enough */
+       strlcpy(entry->d_name, ldir->d_name, sizeof(entry->d_name));
+       *result = entry;
+       return 0;
+}
+
+/* make sure to include all global symbols */
+#include "nolibc.h"
+
+#endif /* _NOLIBC_DIRENT_H */
diff --git a/tools/include/nolibc/nolibc.h b/tools/include/nolibc/nolibc.h
index 
92436b1e44413e659b3cca592930aad8b458cb74..05d92afedb7258f0e3c311bf6f12be68b25d6e9a
 100644
--- a/tools/include/nolibc/nolibc.h
+++ b/tools/include/nolibc/nolibc.h
@@ -105,6 +105,7 @@
 #include "string.h"
 #include "time.h"
 #include "stackprotector.h"
+#include "dirent.h"
 
 /* Used by programs to avoid std includes */
 #define NOLIBC
diff --git a/tools/testing/selftests/nolibc/nolibc-test.c 
b/tools/testing/selftests/nolibc/nolibc-test.c
index 
f162793b162f9b1ec687098b9094a6d247a53e99..798fbdcd3ff8c36b514feb3fa1c7b8d7701cccd7
 100644
--- a/tools/testing/selftests/nolibc/nolibc-test.c
+++ b/tools/testing/selftests/nolibc/nolibc-test.c
@@ -769,6 +769,44 @@ int test_getdents64(const char *dir)
        return ret;
 }
 
+static int test_dirent(void)
+{
+       int comm = 0, cmdline = 0;
+       struct dirent dirent, *result;
+       DIR *dir;
+       int ret;
+
+       dir = opendir("/proc/self");
+       if (!dir)
+               return 1;
+
+       while (1) {
+               errno = 0;
+               ret = readdir_r(dir, &dirent, &result);
+               if (ret != 0)
+                       return 1;
+               if (!result)
+                       break;
+
+               if (strcmp(dirent.d_name, "comm") == 0)
+                       comm++;
+               else if (strcmp(dirent.d_name, "cmdline") == 0)
+                       cmdline++;
+       }
+
+       if (errno)
+               return 1;
+
+       ret = closedir(dir);
+       if (ret)
+               return 1;
+
+       if (comm != 1 || cmdline != 1)
+               return 1;
+
+       return 0;
+}
+
 int test_getpagesize(void)
 {
        int x = getpagesize();
@@ -1061,6 +1099,7 @@ int run_syscall(int min, int max)
                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;
+               CASE_TEST(directories);       EXPECT_SYSZR(proc, 
test_dirent()); break;
                CASE_TEST(gettimeofday_tv);   EXPECT_SYSZR(1, gettimeofday(&tv, 
NULL)); break;
                CASE_TEST(gettimeofday_tv_tz);EXPECT_SYSZR(1, gettimeofday(&tv, 
&tz)); break;
                CASE_TEST(getpagesize);       EXPECT_SYSZR(1, 
test_getpagesize()); break;

-- 
2.48.1


Reply via email to