Merge branch 'fdopendir' into 'master'

Implement fdopendir and fdclosedir

See merge request redox-os/relibc!712
This commit is contained in:
Jeremy Soller
2025-09-17 06:45:51 -06:00
7 changed files with 197 additions and 1 deletions
+43 -1
View File
@@ -16,7 +16,10 @@ use crate::{
platform::{self, types::*, Pal, Sys},
};
use super::errno::{EINVAL, EIO, ENOMEM};
use super::{
errno::{self, EINVAL, EIO, ENOMEM, ENOTDIR},
sys_stat,
};
const INITIAL_BUFSIZE: usize = 512;
@@ -43,6 +46,26 @@ impl DIR {
opaque_offset: 0,
}))
}
pub fn from_fd(fd: c_int) -> Result<Box<Self>, Errno> {
let mut stat = sys_stat::stat::default();
unsafe {
Sys::fstat(fd, &mut stat)?;
}
if (stat.st_mode & sys_stat::S_IFMT) != sys_stat::S_IFDIR {
return Err(Errno(ENOTDIR));
}
Sys::fcntl(fd, fcntl::F_SETFD, fcntl::FD_CLOEXEC as _)?;
// Take ownership now but not earlier so we don't close the fd on error.
let file = File::new(fd);
Ok(Self {
file,
buf: Vec::with_capacity(INITIAL_BUFSIZE),
buf_offset: 0,
opaque_offset: 0,
}
.into())
}
fn next_dirent(&mut self) -> Result<*mut dirent, Errno> {
let mut this_dent = self.buf.get(self.buf_offset..).ok_or(Errno(EIO))?;
if this_dent.is_empty() {
@@ -161,6 +184,19 @@ pub extern "C" fn closedir(dir: Box<DIR>) -> c_int {
dir.close().map(|()| 0).or_minus_one_errno()
}
/// See <https://man.freebsd.org/cgi/man.cgi?query=fdopendir&sektion=3>
///
/// FreeBSD extension that transfers ownership of the directory file descriptor to the user.
///
/// It doesn't matter if DIR was opened with [`opendir`] or [`fdopendir`].
#[no_mangle]
pub extern "C" fn fdclosedir(dir: Box<DIR>) -> c_int {
let mut file = dir.file;
file.reference = true;
*file
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/dirfd.html>.
#[no_mangle]
pub extern "C" fn dirfd(dir: &mut DIR) -> c_int {
@@ -175,6 +211,12 @@ pub unsafe extern "C" fn opendir(path: *const c_char) -> *mut DIR {
DIR::new(path).or_errno_null_mut()
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/fdopendir.html>.
#[no_mangle]
pub extern "C" fn fdopendir(fd: c_int) -> *mut DIR {
DIR::from_fd(fd).or_errno_null_mut()
}
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/posix_getdents.html>.
// #[no_mangle]
pub extern "C" fn posix_getdents(
+1
View File
@@ -27,6 +27,7 @@ EXPECT_NAMES=\
crypt/sha256 \
crypt/sha512 \
destructor \
dirent/fdopendir \
dirent/scandir \
endian \
err \
+153
View File
@@ -0,0 +1,153 @@
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
__attribute__((nonnull))
static bool check_dot(struct dirent* dirent) {
const char* dots[2] = {
".",
".."
};
for (size_t i = 0; i < 2; i++) {
if (strcmp(dots[i], dirent->d_name) == 0) {
return true;
}
}
return false;
}
int main(void) {
int status = EXIT_FAILURE;
char template[] = "/tmp/fdotest.XXXXXX";
if (!mkdtemp(template)) {
perror("mkdtemp");
goto bye;
}
const char* movies[] = {
"big_lebowski",
"blade_runner",
"grand_budapest_hotel",
"taxi_driver"
};
const size_t movies_len = sizeof(movies)/sizeof(char*);
char paths[sizeof(movies)/sizeof(char*)][PATH_MAX] = {0};
for (size_t i = 0; i < movies_len; ++i) {
// Concat the path
const size_t len = sizeof(template) - 1;
char buf[PATH_MAX] = {0};
memcpy(buf, template, len);
buf[len] = '/';
memcpy(&buf[len + 1], movies[i], strlen(movies[i]));
memcpy(paths[i], buf, PATH_MAX);
// And now create the file
int fd = open(buf, O_CREAT);
if (fd == -1) {
perror("open");
goto rmfiles;
}
close(fd);
}
// FIXME: Redox requires read perms for the dir while Linux/BSD don't.
int dir = open(template, O_DIRECTORY | O_RDONLY);
if (dir == -1) {
perror("open");
goto rmfiles;
}
DIR* iter = fdopendir(dir);
if (!iter) {
perror("fdopendir");
goto closedirfd;
}
for (size_t i = 0; i < movies_len; ++i) {
errno = 0;
struct dirent* dirent = readdir(iter);
if (!dirent) {
if (errno) {
perror("readdir");
}
fprintf(
stderr,
"Expected entry #%lu but directory stream is complete\n",
i
);
goto closediriter;
}
// Skip . and ..
if (check_dot(dirent)) {
continue;
}
// Check that the entry matches one of the names.
// readdir's order is indeterministic and looping over the names
// is simpler than qsort for a test.
for (size_t j = 0; j < movies_len; ++j) {
if (strcmp(movies[j], dirent->d_name) == 0) {
goto continue_outer;
}
}
fprintf(
stderr,
"Unexpected entry: %s\n",
dirent->d_name
);
goto closediriter;
continue_outer:
continue;
}
// fdclosedir returns ownership of the original fd.
int returned_fd = fdclosedir(iter);
// Internally, both closedir and fdclosedir consume the boxed DIR.
iter = NULL;
if (returned_fd != dir) {
fputs("fdclosedir returned the wrong descriptor\n", stderr);
goto closedirfd;
}
// Check that the file descriptor is still valid.
struct stat stat = {0};
if (fstatat(returned_fd, "", &stat, AT_EMPTY_PATH) == -1) {
perror("fstatat");
fputs("fdclosedir shouldn't have closed the fd\n", stderr);
goto closedirfd;
}
status = EXIT_SUCCESS;
closediriter:
if (iter) {
closedir(iter);
}
closedirfd:
close(dir);
rmfiles:
for (size_t i = 0; i < movies_len; ++i) {
if (strnlen(paths[i], PATH_MAX) > 4) {
unlink(paths[i]);
}
}
rmdir(template);
bye:
return status;
}