From b610e5b8b8a3a529f024ef66dc0da5591710c942 Mon Sep 17 00:00:00 2001 From: LI Qing Date: Mon, 23 Dec 2019 05:12:21 +0000 Subject: [PATCH] Fix getdents when the next dir entry cannot fit into the output buffer The output buffer given to getdents may not be large enough for the next directory entry. If no directory entries has been loaded into the buffer, just return EINVAL. Otherwise, return the total length of the directory entries already loaded in the buffer --- src/libos/src/fs/mod.rs | 16 ++++--- test/readdir/main.c | 92 +++++++++++++++++++++++++++++++++++------ 2 files changed, 89 insertions(+), 19 deletions(-) diff --git a/src/libos/src/fs/mod.rs b/src/libos/src/fs/mod.rs index 4b83df09..791a4f3a 100644 --- a/src/libos/src/fs/mod.rs +++ b/src/libos/src/fs/mod.rs @@ -206,9 +206,13 @@ pub fn do_getdents64(fd: FileDesc, buf: &mut [u8]) -> Result { Ok(name) => name, }; // TODO: get ino from dirent - let ok = writer.try_write(0, 0, &name); - if !ok { - return_errno!(EINVAL, "the given buffer is too small"); + if let Err(e) = writer.try_write(0, 0, &name) { + file_ref.seek(SeekFrom::Current(-1))?; + if writer.written_size == 0 { + return Err(e); + } else { + break; + } } } Ok(writer.written_size) @@ -570,11 +574,11 @@ impl<'a> DirentBufWriter<'a> { written_size: 0, } } - fn try_write(&mut self, inode: u64, type_: u8, name: &str) -> bool { + fn try_write(&mut self, inode: u64, type_: u8, name: &str) -> Result<()> { let len = ::core::mem::size_of::() + name.len() + 1; let len = (len + 7) / 8 * 8; // align up if self.rest_size < len { - return false; + return_errno!(EINVAL, "the given buffer is too small"); } let dent = LinuxDirent64 { ino: inode, @@ -591,7 +595,7 @@ impl<'a> DirentBufWriter<'a> { } self.rest_size -= len; self.written_size += len; - true + Ok(()) } } diff --git a/test/readdir/main.c b/test/readdir/main.c index 5e07cbfd..ee06d3bd 100644 --- a/test/readdir/main.c +++ b/test/readdir/main.c @@ -1,23 +1,89 @@ #include +#include #include -#include +#include +#include #include #include +#include +#include "test.h" -int main(int argc, const char* argv[]) { - DIR* dirp = opendir("/"); +// ============================================================================ +// The test case of readdir +// ============================================================================ + +static int test_readdir() { + struct dirent *dp; + DIR* dirp; + + dirp = opendir("/"); if (dirp == NULL) { - printf("failed to open directory at /\n"); - return -1; + THROW_ERROR("failed to open directory"); + } + while (1) { + errno = 0; + dp = readdir(dirp); + if (dp == NULL) { + if (errno != 0) { + closedir(dirp); + THROW_ERROR("faild to call readdir"); + } + break; + } } - - struct dirent *dp; - while ((dp = readdir(dirp)) != NULL) { - printf("get: %s\n", dp->d_name); - } - closedir(dirp); - - printf("Read directory test successful\n"); return 0; } + +static int test_getdents_with_big_enough_buffer() { + int fd, len; + char buf[64]; + + fd = open("/", O_RDONLY | O_DIRECTORY); + if (fd < 0) { + THROW_ERROR("failed to open directory"); + } + while (1) { + len = getdents(fd, (struct dirent *)buf, sizeof(buf)); + if (len < 0) { + close(fd); + THROW_ERROR("failed to call getdents"); + } else if (len == 0) { + // On end of directory, 0 is returned + break; + } + } + close(fd); + return 0; +} + +static int test_getdents_with_too_small_buffer() { + int fd, len; + char buf[4]; + + fd = open("/", O_RDONLY | O_DIRECTORY); + if (fd < 0) { + THROW_ERROR("failed to open directory"); + } + len = getdents(fd, (struct dirent *)buf, sizeof(buf)); + if (len >= 0 || errno != EINVAL) { + close(fd); + THROW_ERROR("failed to call getdents with small buffer"); + } + close(fd); + return 0; +} + +// ============================================================================ +// Test suite main +// ============================================================================ + +static test_case_t test_cases[] = { + TEST_CASE(test_readdir), + TEST_CASE(test_getdents_with_big_enough_buffer), + TEST_CASE(test_getdents_with_too_small_buffer), +}; + +int main() { + return test_suite_run(test_cases, ARRAY_SIZE(test_cases)); +}