From 758f681590831290351ee8345c4b795fadfed733 Mon Sep 17 00:00:00 2001
From: jD91mZM2 <me@krake.one>
Date: Sun, 7 Oct 2018 14:43:54 +0200
Subject: [PATCH] Implement scandir

---
 src/header/dirent/mod.rs             | 110 ++++++++++++++++++++++-----
 tests/Makefile                       |   3 +-
 tests/{dirent.c => dirent/main.c}    |   0
 tests/dirent/scandir.c               |  23 ++++++
 tests/expected/dirent/scandir.stderr |   0
 tests/expected/dirent/scandir.stdout |   9 +++
 6 files changed, 126 insertions(+), 19 deletions(-)
 rename tests/{dirent.c => dirent/main.c} (100%)
 create mode 100644 tests/dirent/scandir.c
 create mode 100644 tests/expected/dirent/scandir.stderr
 create mode 100644 tests/expected/dirent/scandir.stdout

diff --git a/src/header/dirent/mod.rs b/src/header/dirent/mod.rs
index 7ea6e363..b2a133bd 100644
--- a/src/header/dirent/mod.rs
+++ b/src/header/dirent/mod.rs
@@ -4,19 +4,20 @@ use alloc::boxed::Box;
 use core::{mem, ptr};
 
 use c_str::CStr;
-use header::{errno, fcntl, unistd};
-use platform;
+use fs::File;
+use header::{errno, fcntl, stdlib, string};
+use io::{Seek, SeekFrom};
 use platform::types::*;
 use platform::{Pal, Sys};
+use platform;
 
 const DIR_BUF_SIZE: usize = mem::size_of::<dirent>() * 3;
 
 // No repr(C) needed, C won't see the content
-// TODO: ***THREAD SAFETY***
 pub struct DIR {
-    fd: c_int,
+    file: File,
     buf: [c_char; DIR_BUF_SIZE],
-    // index & len are specified in bytes
+    // index and len are specified in bytes
     index: usize,
     len: usize,
 
@@ -26,6 +27,7 @@ pub struct DIR {
 }
 
 #[repr(C)]
+#[derive(Clone)]
 pub struct dirent {
     pub d_ino: ino_t,
     pub d_off: off_t,
@@ -37,18 +39,16 @@ pub struct dirent {
 #[no_mangle]
 pub extern "C" fn opendir(path: *const c_char) -> *mut DIR {
     let path = unsafe { CStr::from_ptr(path) };
-    let fd = Sys::open(
+    let file = match File::open(
         path,
-        fcntl::O_RDONLY | fcntl::O_DIRECTORY | fcntl::O_CLOEXEC,
-        0,
-    );
-
-    if fd < 0 {
-        return ptr::null_mut();
-    }
+        fcntl::O_RDONLY | fcntl::O_DIRECTORY | fcntl::O_CLOEXEC
+    ) {
+        Ok(file) => file,
+        Err(_) => return ptr::null_mut()
+    };
 
     Box::into_raw(Box::new(DIR {
-        fd,
+        file,
         buf: [0; DIR_BUF_SIZE],
         index: 0,
         len: 0,
@@ -58,8 +58,13 @@ pub extern "C" fn opendir(path: *const c_char) -> *mut DIR {
 
 #[no_mangle]
 pub unsafe extern "C" fn closedir(dir: *mut DIR) -> c_int {
-    let ret = Sys::close((*dir).fd);
-    Box::from_raw(dir);
+    let mut dir = Box::from_raw(dir);
+
+    let ret = Sys::close(*dir.file);
+
+    // Reference files aren't closed when dropped
+    dir.file.reference = true;
+
     ret
 }
 
@@ -67,7 +72,7 @@ pub unsafe extern "C" fn closedir(dir: *mut DIR) -> c_int {
 pub unsafe extern "C" fn readdir(dir: *mut DIR) -> *mut dirent {
     if (*dir).index >= (*dir).len {
         let read = Sys::getdents(
-            (*dir).fd,
+            *(*dir).file,
             (*dir).buf.as_mut_ptr() as *mut dirent,
             (*dir).buf.len(),
         );
@@ -116,7 +121,7 @@ pub unsafe extern "C" fn telldir(dir: *mut DIR) -> c_long {
 }
 #[no_mangle]
 pub unsafe extern "C" fn seekdir(dir: *mut DIR, off: c_long) {
-    unistd::lseek((*dir).fd, off, unistd::SEEK_SET);
+    let _ = (*dir).file.seek(SeekFrom::Start(off as u64));
     (*dir).offset = off as usize;
     (*dir).index = 0;
     (*dir).len = 0;
@@ -125,3 +130,72 @@ pub unsafe extern "C" fn seekdir(dir: *mut DIR, off: c_long) {
 pub unsafe extern "C" fn rewinddir(dir: *mut DIR) {
     seekdir(dir, 0)
 }
+
+#[no_mangle]
+pub unsafe extern "C" fn alphasort(
+    first: *mut *const dirent,
+    second: *mut *const dirent
+) -> c_int {
+    string::strcoll((**first).d_name.as_ptr(), (**second).d_name.as_ptr())
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn scandir(
+    dirp: *const c_char,
+    namelist: *mut *mut *mut dirent,
+    filter: Option<extern "C" fn(_: *const dirent) -> c_int>,
+    compare: Option<extern "C" fn(_: *mut *const dirent, _: *mut *const dirent) -> c_int>
+) -> c_int {
+    let dir = opendir(dirp);
+    if dir.is_null() {
+        return -1;
+    }
+
+    let old_errno = platform::errno;
+
+    let mut len: isize = 0;
+    let mut cap: isize = 4;
+    *namelist = platform::alloc(cap as usize * mem::size_of::<*mut dirent>()) as *mut *mut dirent;
+
+    loop {
+        platform::errno = 0;
+        let entry = readdir(dir);
+        if entry.is_null() {
+            break;
+        }
+
+        if let Some(filter) = filter {
+            if filter(entry) == 0 {
+                continue;
+            }
+        }
+
+        if len >= cap {
+            cap *= 2;
+            *namelist = platform::realloc(
+                *namelist as *mut c_void,
+                cap as usize * mem::size_of::<*mut dirent>()
+            ) as *mut *mut dirent;
+        }
+
+        let copy = platform::alloc(mem::size_of::<dirent>()) as *mut dirent;
+        *copy = (*entry).clone();
+        *(*namelist).offset(len) = copy;
+        len += 1;
+    }
+
+    closedir(dir);
+
+    if platform::errno != 0 {
+        while len > 0 {
+            len -= 1;
+            platform::free(*(*namelist).offset(len) as *mut c_void);
+        }
+        platform::free(*namelist as *mut c_void);
+        -1
+    } else {
+        platform::errno = old_errno;
+        stdlib::qsort(*namelist as *mut c_void, len as size_t, mem::size_of::<*mut dirent>(), mem::transmute(compare));
+        len as c_int
+    }
+}
diff --git a/tests/Makefile b/tests/Makefile
index 71ee5e05..a7bdb63a 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -4,6 +4,7 @@ EXPECT_BINS=\
 	arpainet \
 	assert \
 	ctype \
+	dirent/scandir \
 	error \
 	fcntl/create \
 	fcntl/fcntl \
@@ -80,7 +81,7 @@ EXPECT_BINS=\
 # Binaries that may generate varied output
 BINS=\
 	$(EXPECT_BINS) \
-	dirent \
+	dirent/main \
 	pwd \
 	stdlib/alloc \
 	stdlib/bsearch \
diff --git a/tests/dirent.c b/tests/dirent/main.c
similarity index 100%
rename from tests/dirent.c
rename to tests/dirent/main.c
diff --git a/tests/dirent/scandir.c b/tests/dirent/scandir.c
new file mode 100644
index 00000000..383a6ca0
--- /dev/null
+++ b/tests/dirent/scandir.c
@@ -0,0 +1,23 @@
+#include <dirent.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+int filter(const struct dirent* dirent) {
+    return strstr(dirent->d_name, "3") == NULL;
+}
+
+int main() {
+    struct dirent** array;
+    int len = scandir("example_dir/", &array, filter, alphasort);
+    if (len < 0) {
+        perror("scandir");
+        return -1;
+    }
+
+    for(int i = 0; i < len; i += 1) {
+        puts(array[i]->d_name);
+        free(array[i]);
+    }
+    free(array);
+}
diff --git a/tests/expected/dirent/scandir.stderr b/tests/expected/dirent/scandir.stderr
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/expected/dirent/scandir.stdout b/tests/expected/dirent/scandir.stdout
new file mode 100644
index 00000000..c60de319
--- /dev/null
+++ b/tests/expected/dirent/scandir.stdout
@@ -0,0 +1,9 @@
+.
+..
+1-never-gonna-give-you-up
+2-never-gonna-let-you-down
+4-and-desert-you
+5-never-gonna-make-you-cry
+6-never-gonna-say-goodbye
+7-never-gonna-tell-a-lie
+8-and-hurt-you
-- 
GitLab