Created
October 22, 2023 12:00
-
-
Save sagoez/f2030bd6d53520039637631559b9f8c0 to your computer and use it in GitHub Desktop.
Solution: Safe FFI Wrapper
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Exercise: https://google.github.io/comprehensive-rust/exercises/day-3/safe-ffi-wrapper.html | |
mod ffi { | |
use std::os::raw::{c_char, c_int}; | |
#[cfg(not(target_os = "macos"))] | |
use std::os::raw::{c_long, c_uchar, c_ulong, c_ushort}; | |
// Opaque type. See https://doc.rust-lang.org/nomicon/ffi.html. | |
#[repr(C)] | |
pub struct DIR { | |
_data: [u8; 0], | |
_marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>, | |
} | |
// Layout according to the Linux man page for readdir(3), where ino_t and | |
// off_t are resolved according to the definitions in | |
// /usr/include/x86_64-linux-gnu/{sys/types.h, bits/typesizes.h}. | |
#[cfg(not(target_os = "macos"))] | |
#[repr(C)] | |
pub struct dirent { | |
pub d_ino: c_ulong, | |
pub d_off: c_long, | |
pub d_reclen: c_ushort, | |
pub d_type: c_uchar, | |
pub d_name: [c_char; 256], | |
} | |
// Layout according to the macOS man page for dir(5). | |
#[cfg(target_os = "macos")] | |
#[repr(C)] | |
pub struct dirent { | |
pub d_fileno: u64, | |
pub d_seekoff: u64, | |
pub d_reclen: u16, | |
pub d_namlen: u16, | |
pub d_type: u8, | |
pub d_name: [c_char; 1024], | |
} | |
extern "C" { | |
pub fn opendir(s: *const c_char) -> *mut DIR; | |
#[cfg(not(all(target_os = "macos", target_arch = "x86_64")))] | |
pub fn readdir(s: *mut DIR) -> *const dirent; | |
// See https://github.com/rust-lang/libc/issues/414 and the section on | |
// _DARWIN_FEATURE_64_BIT_INODE in the macOS man page for stat(2). | |
// | |
// "Platforms that existed before these updates were available" refers | |
// to macOS (as opposed to iOS / wearOS / etc.) on Intel and PowerPC. | |
#[cfg(all(target_os = "macos", target_arch = "x86_64"))] | |
#[link_name = "readdir$INODE64"] | |
pub fn readdir(s: *mut DIR) -> *const dirent; | |
pub fn closedir(s: *mut DIR) -> c_int; | |
} | |
} | |
use std::ffi::{CStr, CString, OsStr, OsString}; | |
use std::os::unix::ffi::OsStrExt; | |
#[derive(Debug)] | |
struct DirectoryIterator { | |
path: CString, | |
dir: *mut ffi::DIR, | |
} | |
impl DirectoryIterator { | |
fn new(path: &str) -> Result<DirectoryIterator, String> { | |
// Call opendir and return a Ok value if that worked, | |
// otherwise return Err with a message. | |
// Hint: you will need to convert the path to a CString. | |
let path = CString::new(path).map_err(|err| format!("path is not valid: {}", err))?; | |
let dir = unsafe { ffi::opendir(path.as_ptr()) }; | |
if dir.is_null() { | |
return Err("opendir failed".to_string()); | |
} | |
Ok(DirectoryIterator { path, dir }) | |
} | |
} | |
impl Iterator for DirectoryIterator { | |
type Item = OsString; | |
fn next(&mut self) -> Option<OsString> { | |
// Keep calling readdir until we get a NULL pointer back. | |
// If we get a NULL pointer, return None. | |
// Otherwise, return Some with the name of the file. | |
let dirent: *const ffi::dirent = unsafe { ffi::readdir(self.dir) }; | |
if dirent.is_null() { | |
return None; | |
} | |
// SAFETY: We just checked that dirent is not null so it is safe to dereference. | |
let dir_name: &CStr = unsafe { CStr::from_ptr((*dirent).d_name.as_ptr()) }; | |
Some(OsStr::from_bytes(dir_name.to_bytes()).into()) | |
} | |
} | |
impl Drop for DirectoryIterator { | |
fn drop(&mut self) { | |
// Call closedir as needed. | |
if !self.dir.is_null() && unsafe { ffi::closedir(self.dir) } != 0 { | |
panic!("closedir failed"); | |
} | |
} | |
} | |
fn main() -> Result<(), String> { | |
let iter = DirectoryIterator::new(".")?; | |
println!("files: {:#?}", iter.collect::<Vec<_>>()); | |
Ok(()) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment