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
This commit is contained in:
LI Qing 2019-12-23 05:12:21 +00:00 committed by Tate, Hongliang Tian
parent 6d7597c25e
commit b610e5b8b8
2 changed files with 89 additions and 19 deletions

@ -206,9 +206,13 @@ pub fn do_getdents64(fd: FileDesc, buf: &mut [u8]) -> Result<usize> {
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::<LinuxDirent64>() + 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(())
}
}

@ -1,23 +1,89 @@
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#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));
}