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 <thomas.weissschuh@linutronix.de>
Link: https://lore.kernel.org/r/20250209-nolibc-dir-v2-2-57cc1da8558b@weissschuh.net
Signed-off-by: Thomas Weißschuh <linux@weissschuh.net>
This commit is contained in:
Thomas Weißschuh 2025-02-09 14:25:46 +01:00 committed by Thomas Weißschuh
parent dde5625d4d
commit 665fa8dea9
4 changed files with 139 additions and 0 deletions

View File

@ -29,6 +29,7 @@ all_files := \
compiler.h \
crt.h \
ctype.h \
dirent.h \
errno.h \
nolibc.h \
signal.h \

View File

@ -0,0 +1,98 @@
/* SPDX-License-Identifier: LGPL-2.1 OR MIT */
/*
* Directory access for NOLIBC
* Copyright (C) 2025 Thomas Weißschuh <linux@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 */

View File

@ -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

View File

@ -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;