Skip to content

Commit 665fa8d

Browse files
committed
tools/nolibc: add support for directory access
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 <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Thomas Weißschuh <[email protected]>
1 parent dde5625 commit 665fa8d

File tree

4 files changed

+139
-0
lines changed

4 files changed

+139
-0
lines changed

tools/include/nolibc/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ all_files := \
2929
compiler.h \
3030
crt.h \
3131
ctype.h \
32+
dirent.h \
3233
errno.h \
3334
nolibc.h \
3435
signal.h \

tools/include/nolibc/dirent.h

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/* SPDX-License-Identifier: LGPL-2.1 OR MIT */
2+
/*
3+
* Directory access for NOLIBC
4+
* Copyright (C) 2025 Thomas Weißschuh <[email protected]>
5+
*/
6+
7+
#ifndef _NOLIBC_DIRENT_H
8+
#define _NOLIBC_DIRENT_H
9+
10+
#include "stdint.h"
11+
#include "types.h"
12+
13+
#include <linux/limits.h>
14+
15+
struct dirent {
16+
ino_t d_ino;
17+
char d_name[NAME_MAX + 1];
18+
};
19+
20+
/* See comment of FILE in stdio.h */
21+
typedef struct {
22+
char dummy[1];
23+
} DIR;
24+
25+
static __attribute__((unused))
26+
DIR *fdopendir(int fd)
27+
{
28+
if (fd < 0) {
29+
SET_ERRNO(EBADF);
30+
return NULL;
31+
}
32+
return (DIR *)(intptr_t)~fd;
33+
}
34+
35+
static __attribute__((unused))
36+
DIR *opendir(const char *name)
37+
{
38+
int fd;
39+
40+
fd = open(name, O_RDONLY);
41+
if (fd == -1)
42+
return NULL;
43+
return fdopendir(fd);
44+
}
45+
46+
static __attribute__((unused))
47+
int closedir(DIR *dirp)
48+
{
49+
intptr_t i = (intptr_t)dirp;
50+
51+
if (i >= 0) {
52+
SET_ERRNO(EBADF);
53+
return -1;
54+
}
55+
return close(~i);
56+
}
57+
58+
static __attribute__((unused))
59+
int readdir_r(DIR *dirp, struct dirent *entry, struct dirent **result)
60+
{
61+
char buf[sizeof(struct linux_dirent64) + NAME_MAX + 1];
62+
struct linux_dirent64 *ldir = (void *)buf;
63+
intptr_t i = (intptr_t)dirp;
64+
int fd, ret;
65+
66+
if (i >= 0)
67+
return EBADF;
68+
69+
fd = ~i;
70+
71+
ret = sys_getdents64(fd, ldir, sizeof(buf));
72+
if (ret < 0)
73+
return -ret;
74+
if (ret == 0) {
75+
*result = NULL;
76+
return 0;
77+
}
78+
79+
/*
80+
* getdents64() returns as many entries as fit the buffer.
81+
* readdir() can only return one entry at a time.
82+
* Make sure the non-returned ones are not skipped.
83+
*/
84+
ret = lseek(fd, ldir->d_off, SEEK_SET);
85+
if (ret == -1)
86+
return errno;
87+
88+
entry->d_ino = ldir->d_ino;
89+
/* the destination should always be big enough */
90+
strlcpy(entry->d_name, ldir->d_name, sizeof(entry->d_name));
91+
*result = entry;
92+
return 0;
93+
}
94+
95+
/* make sure to include all global symbols */
96+
#include "nolibc.h"
97+
98+
#endif /* _NOLIBC_DIRENT_H */

tools/include/nolibc/nolibc.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@
105105
#include "string.h"
106106
#include "time.h"
107107
#include "stackprotector.h"
108+
#include "dirent.h"
108109

109110
/* Used by programs to avoid std includes */
110111
#define NOLIBC

tools/testing/selftests/nolibc/nolibc-test.c

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -769,6 +769,44 @@ int test_getdents64(const char *dir)
769769
return ret;
770770
}
771771

772+
static int test_dirent(void)
773+
{
774+
int comm = 0, cmdline = 0;
775+
struct dirent dirent, *result;
776+
DIR *dir;
777+
int ret;
778+
779+
dir = opendir("/proc/self");
780+
if (!dir)
781+
return 1;
782+
783+
while (1) {
784+
errno = 0;
785+
ret = readdir_r(dir, &dirent, &result);
786+
if (ret != 0)
787+
return 1;
788+
if (!result)
789+
break;
790+
791+
if (strcmp(dirent.d_name, "comm") == 0)
792+
comm++;
793+
else if (strcmp(dirent.d_name, "cmdline") == 0)
794+
cmdline++;
795+
}
796+
797+
if (errno)
798+
return 1;
799+
800+
ret = closedir(dir);
801+
if (ret)
802+
return 1;
803+
804+
if (comm != 1 || cmdline != 1)
805+
return 1;
806+
807+
return 0;
808+
}
809+
772810
int test_getpagesize(void)
773811
{
774812
int x = getpagesize();
@@ -1061,6 +1099,7 @@ int run_syscall(int min, int max)
10611099
CASE_TEST(fork); EXPECT_SYSZR(1, test_fork()); break;
10621100
CASE_TEST(getdents64_root); EXPECT_SYSNE(1, test_getdents64("/"), -1); break;
10631101
CASE_TEST(getdents64_null); EXPECT_SYSER(1, test_getdents64("/dev/null"), -1, ENOTDIR); break;
1102+
CASE_TEST(directories); EXPECT_SYSZR(proc, test_dirent()); break;
10641103
CASE_TEST(gettimeofday_tv); EXPECT_SYSZR(1, gettimeofday(&tv, NULL)); break;
10651104
CASE_TEST(gettimeofday_tv_tz);EXPECT_SYSZR(1, gettimeofday(&tv, &tz)); break;
10661105
CASE_TEST(getpagesize); EXPECT_SYSZR(1, test_getpagesize()); break;

0 commit comments

Comments
 (0)