Skip to content

Commit

Permalink
Add phtread_key and readdir (#132)
Browse files Browse the repository at this point in the history
* Add __stack_chk_fail

* Add pthread_key

* Add readdir

* Format

* Remove change that doesn't really do much

* format

* Fix build

* Test

* Actaully commit the rest of it

* Don't gate the macros

* Address review comments

* Implement epoch-system for keys

* Bump PTHREAD_KEYS_MAX, fix build

* Fix deletion

* Fix other deletion

* Fix nits

* Move comment

* Format

* Check for epoch overflow
  • Loading branch information
carbotaniuman authored Jul 27, 2022
1 parent c1ce132 commit e891c1d
Show file tree
Hide file tree
Showing 6 changed files with 266 additions and 56 deletions.
7 changes: 6 additions & 1 deletion c-scape/src/fs/dir/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,13 @@ mod readdir;

use rustix::io::OwnedFd;

union LibcDirStorage {
dirent: libc::dirent,
dirent64: libc::dirent64,
}

struct CScapeDir {
dir: rustix::fs::Dir,
dirent: libc::dirent64,
storage: LibcDirStorage,
fd: OwnedFd,
}
2 changes: 1 addition & 1 deletion c-scape/src/fs/dir/opendir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ unsafe extern "C" fn fdopendir(fd: c_int) -> *mut c_void {
match convert_res(rustix::fs::Dir::read_from(BorrowedFd::borrow_raw(fd))) {
Some(dir) => Box::into_raw(Box::new(CScapeDir {
dir,
dirent: zeroed(),
storage: zeroed(),
fd: OwnedFd::from_raw_fd(fd),
}))
.cast(),
Expand Down
64 changes: 56 additions & 8 deletions c-scape/src/fs/dir/readdir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@ unsafe extern "C" fn readdir64_r(
}

#[no_mangle]
unsafe extern "C" fn readdir64(dir: *mut c_void) -> *mut libc::dirent64 {
libc!(libc::readdir64(dir.cast(),));
unsafe extern "C" fn readdir64(dir: *mut libc::DIR) -> *mut libc::dirent64 {
libc!(libc::readdir64(dir.cast()));

let mustang_dir = dir.cast::<CScapeDir>();
let dir = &mut (*mustang_dir).dir;
Expand All @@ -71,7 +71,7 @@ unsafe extern "C" fn readdir64(dir: *mut c_void) -> *mut libc::dirent64 {
rustix::fs::FileType::BlockDevice => libc::DT_BLK,
rustix::fs::FileType::Unknown => libc::DT_UNKNOWN,
};
(*mustang_dir).dirent = libc::dirent64 {
(*mustang_dir).storage.dirent64 = libc::dirent64 {
d_ino: e.ino(),
d_off: 0, // We don't implement `seekdir` yet anyway.
d_reclen: (offset_of!(libc::dirent64, d_name) + e.file_name().to_bytes().len() + 1)
Expand All @@ -81,9 +81,9 @@ unsafe extern "C" fn readdir64(dir: *mut c_void) -> *mut libc::dirent64 {
d_name: [0; 256],
};
let len = core::cmp::min(256, e.file_name().to_bytes().len());
(*mustang_dir).dirent.d_name[..len]
(*mustang_dir).storage.dirent64.d_name[..len]
.copy_from_slice(transmute(e.file_name().to_bytes()));
&mut (*mustang_dir).dirent
&mut (*mustang_dir).storage.dirent64
}
Some(Err(err)) => {
set_errno(Errno(err.raw_os_error()));
Expand All @@ -93,9 +93,57 @@ unsafe extern "C" fn readdir64(dir: *mut c_void) -> *mut libc::dirent64 {
}

#[no_mangle]
unsafe extern "C" fn readdir() {
//libc!(libc::readdir());
unimplemented!("readdir")
unsafe extern "C" fn readdir(dir: *mut libc::DIR) -> *mut libc::dirent {
libc!(libc::readdir(dir.cast()));

let mustang_dir = dir.cast::<CScapeDir>();
let dir = &mut (*mustang_dir).dir;
match dir.read() {
None => null_mut(),
Some(Ok(e)) => {
let file_type = match e.file_type() {
rustix::fs::FileType::RegularFile => libc::DT_REG,
rustix::fs::FileType::Directory => libc::DT_DIR,
rustix::fs::FileType::Symlink => libc::DT_LNK,
rustix::fs::FileType::Fifo => libc::DT_FIFO,
rustix::fs::FileType::Socket => libc::DT_SOCK,
rustix::fs::FileType::CharacterDevice => libc::DT_CHR,
rustix::fs::FileType::BlockDevice => libc::DT_BLK,
rustix::fs::FileType::Unknown => libc::DT_UNKNOWN,
};

let result: Result<(), core::num::TryFromIntError> = try {
(*mustang_dir).storage.dirent = libc::dirent {
d_ino: e.ino().try_into()?,
d_off: 0, // We don't implement `seekdir` yet anyway.
d_reclen: (offset_of!(libc::dirent64, d_name)
+ e.file_name().to_bytes().len()
+ 1)
.try_into()
.unwrap(),
d_type: file_type,
d_name: [0; 256],
};
};

match result {
Err(_) => {
set_errno(Errno(libc::EOVERFLOW));
return null_mut();
}
Ok(()) => {}
}

let len = core::cmp::min(256, e.file_name().to_bytes().len());
(*mustang_dir).storage.dirent.d_name[..len]
.copy_from_slice(transmute(e.file_name().to_bytes()));
&mut (*mustang_dir).storage.dirent
}
Some(Err(err)) => {
set_errno(Errno(err.raw_os_error()));
null_mut()
}
}
}

#[no_mangle]
Expand Down
6 changes: 6 additions & 0 deletions c-scape/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#![feature(untagged_unions)] // for `PthreadMutexT`
#![feature(atomic_mut_ptr)] // for `RawMutex`
#![feature(strict_provenance)]
#![feature(inline_const)]
#![feature(sync_unsafe_cell)]
#![feature(core_c_str)] // for `core::ffi::CStr`
#![deny(fuzzy_provenance_casts)]
Expand Down Expand Up @@ -382,6 +383,11 @@ unsafe extern "C" fn abort() {
unimplemented!("abort")
}

#[no_mangle]
unsafe extern "C" fn __stack_chk_fail() {
unimplemented!("__stack_chk_fail")
}

#[no_mangle]
unsafe extern "C" fn signal(_num: c_int, _handler: usize) -> usize {
libc!(libc::signal(_num, _handler));
Expand Down
179 changes: 179 additions & 0 deletions c-scape/src/threads/key.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
use alloc::boxed::Box;
use core::cell::Cell;
use core::ptr::{null, null_mut};
use core::sync::atomic::{AtomicU32, Ordering};
use errno::{set_errno, Errno};
use libc::{c_int, c_void};

use origin::sync::RwLock;

#[cfg(target_env = "gnu")]
const PTHREAD_KEYS_MAX: u32 = 1024;
const PTHREAD_DESTRUCTOR_ITERATIONS: u8 = 4;

#[derive(Clone, Copy)]
struct KeyData {
next_key: libc::pthread_key_t,
destructors: [Option<unsafe extern "C" fn(_: *mut c_void)>; PTHREAD_KEYS_MAX as usize],
}

#[derive(Clone, Copy)]
struct ValueWithEpoch {
epoch: u32,
data: *mut c_void,
}

impl ValueWithEpoch {
const fn new() -> Self {
ValueWithEpoch {
epoch: 0,
data: null_mut(),
}
}
}

static KEY_DATA: RwLock<KeyData> = RwLock::new(KeyData {
next_key: 0,
destructors: [None; PTHREAD_KEYS_MAX as usize],
});

static EPOCHS: [AtomicU32; PTHREAD_KEYS_MAX as usize] =
[const { AtomicU32::new(0) }; PTHREAD_KEYS_MAX as usize];

// This uses an epoch-based system for differentiating between
// reused keys corresponding to the same slot.
#[thread_local]
static VALUES: [Cell<ValueWithEpoch>; PTHREAD_KEYS_MAX as usize] =
[const { Cell::new(ValueWithEpoch::new()) }; PTHREAD_KEYS_MAX as usize];

#[thread_local]
static HAS_REGISTERED_CLEANUP: Cell<bool> = Cell::new(false);

#[no_mangle]
unsafe extern "C" fn pthread_getspecific(key: libc::pthread_key_t) -> *mut c_void {
libc!(libc::pthread_getspecific(key));

let latest_epoch = EPOCHS[key as usize].load(Ordering::SeqCst);
let ValueWithEpoch { epoch, data } = VALUES[key as usize].get();

// If the latest epoch is newer, then that means this slot got reallocated.
// Either we are reusing a deleted key, which is UB, or we are reusing
// the new key, which must return null initially.
if epoch < latest_epoch {
null_mut()
} else {
data
}
}

#[no_mangle]
unsafe extern "C" fn pthread_setspecific(key: libc::pthread_key_t, value: *const c_void) -> c_int {
libc!(libc::pthread_setspecific(key, value));

// If this is the first-time we have gotten here,
// we need to actually register the dtors for cleanup.
if !HAS_REGISTERED_CLEANUP.get() {
origin::at_thread_exit(Box::new(move || {
for _ in 0..PTHREAD_DESTRUCTOR_ITERATIONS {
let mut ran_dtor = false;

for i in 0..PTHREAD_KEYS_MAX {
let data = pthread_getspecific(i as libc::pthread_key_t);

if data.is_null() {
continue;
}

ran_dtor = true;

let dtor = {
// POSIX says that `pthread_key_delete` can
// be called within a destructor function...
// We have to take each dtor one at a time just
// in case someone did delete it;
let key_data = KEY_DATA.read();
key_data.destructors[i as usize]
};

if let Some(dtor) = dtor {
// Null out the data as required
// by POSIX semantics. This may bump
// the local epoch, but that doesn't matter.
pthread_setspecific(i as libc::pthread_key_t, null());

// Call the destructor with the old
// data, before we set it to null.
dtor(value as *mut _);
}
}

if !ran_dtor {
break;
}
}
}));

HAS_REGISTERED_CLEANUP.set(true);
}

VALUES[key as usize].set(ValueWithEpoch {
epoch: EPOCHS[key as usize].load(Ordering::SeqCst),
data: value as *mut _,
});
0
}

#[no_mangle]
unsafe extern "C" fn pthread_key_create(
key: *mut libc::pthread_key_t,
dtor: Option<unsafe extern "C" fn(_: *mut c_void)>,
) -> c_int {
libc!(libc::pthread_key_create(key, dtor));

extern "C" fn empty_dtor(_: *mut c_void) {}

let mut key_data = KEY_DATA.write();

let mut next_key = key_data.next_key;
if next_key < PTHREAD_KEYS_MAX {
// Fast-path, less than `PTHREAD_KEYS_MAX` slots
// have been allocated, we just use this as a bump
// allocator basically.
key_data.next_key = next_key + 1;
} else {
// Slow-path, linearly scan through the table to try
// and find an empty slot.
for (index, dtor) in key_data.destructors.iter().enumerate() {
if dtor.is_none() {
// We have to bump the epoch now that we are
// reusing slots.
if EPOCHS[index].fetch_add(1, Ordering::SeqCst) == 0 {
panic!("detected epoch counter overflow");
}
next_key = index as libc::pthread_key_t;
}
}

// If the loop still did not find a valid key
if next_key >= PTHREAD_KEYS_MAX {
set_errno(Errno(libc::EAGAIN));
return -1;
}
}

// We have to `unwrap_or` the dtor because `None` is reserved for signifying
// that the key is not allocated.
key_data.destructors[next_key as usize] = Some(dtor.unwrap_or(empty_dtor));

0
}

#[no_mangle]
unsafe extern "C" fn pthread_key_delete(key: libc::pthread_key_t) -> c_int {
libc!(libc::pthread_key_delete(key));

let mut key_data = KEY_DATA.write();
key_data.destructors[key as usize] = None;

0
}
Loading

0 comments on commit e891c1d

Please sign in to comment.