use std::{
ffi::{c_int, c_void},
io::{self, Read, Seek, Write},
mem::ManuallyDrop,
os::fd::{FromRawFd, OwnedFd},
ptr::NonNull,
};
use super::{bindings::gpgme_io_event_done_data, *};
#[derive(Debug)]
#[repr(C)]
struct TagData {
idx: usize,
fd: c_int,
io_state: Arc<Mutex<IoState>>,
}
#[repr(transparent)]
struct IoStateWrapper(ManuallyDrop<Arc<Mutex<IoState>>>);
impl IoStateWrapper {
unsafe fn from_raw(ptr: *mut c_void) -> Self {
let io_state: Arc<Mutex<IoState>> =
unsafe { Arc::from_raw(ptr.cast_const().cast::<Mutex<IoState>>()) };
Self(ManuallyDrop::new(io_state))
}
}
impl Drop for IoStateWrapper {
fn drop(&mut self) {
let inner = unsafe { ManuallyDrop::take(&mut self.0) };
let _ = Arc::into_raw(inner);
}
}
pub unsafe extern "C" fn gpgme_register_io_cb(
data: *mut c_void,
fd: c_int,
dir: c_int,
fnc: gpgme_io_cb_t,
fnc_data: *mut c_void,
tag: *mut *mut c_void,
) -> gpgme_error_t {
let io_state: IoStateWrapper = unsafe { IoStateWrapper::from_raw(data) };
let io_state_copy = Arc::clone(&io_state.0);
if let Ok(mut io_state_lck) = io_state.0.lock() {
let idx = io_state_lck.max_idx;
io_state_lck.max_idx += 1;
let (sender, receiver) = smol::channel::unbounded();
let gpgfd = {
let fd = unsafe { OwnedFd::from_raw_fd(fd) };
GpgmeFd {
fd: ManuallyDrop::new(fd).into(),
fnc,
fnc_data,
idx,
write: dir == 0,
sender,
receiver,
io_state: io_state_copy.clone(),
}
};
{
let tag_data = Arc::into_raw(Arc::new(TagData {
idx,
fd,
io_state: io_state_copy,
}));
unsafe { std::ptr::write(tag, tag_data.cast_mut().cast::<c_void>()) };
}
io_state_lck.ops.insert(idx, gpgfd);
}
0
}
pub unsafe extern "C" fn gpgme_remove_io_cb(tag: *mut c_void) {
let tag_data: Arc<TagData> = unsafe { Arc::from_raw(tag.cast_const().cast::<TagData>()) };
let gpgfd: GpgmeFd = {
let Ok(mut io_state_lck) = tag_data.io_state.lock() else {
return;
};
io_state_lck.ops.remove(&tag_data.idx).unwrap_or_else(|| {
panic!(
"gpgme_remove_io_cb called with tag_data {:?}, but idx {} is not included in \
io_state ops. This is a bug.",
tag_data, tag_data.idx
)
})
};
_ = gpgfd.sender.try_send(());
}
pub unsafe extern "C" fn gpgme_event_io_cb(
data: *mut c_void,
r#type: gpgme_event_io_t,
type_data: *mut c_void,
) {
if r#type == gpgme_event_io_t::GPGME_EVENT_START {
return;
}
let io_state: IoStateWrapper = unsafe { IoStateWrapper::from_raw(data) };
if r#type == gpgme_event_io_t::GPGME_EVENT_DONE {
let Some(status) = NonNull::new(type_data.cast::<gpgme_io_event_done_data>()) else {
log::error!("gpgme_event_io_cb DONE event with NULL type_data. This is a gpgme bug.",);
return;
};
let err = unsafe { status.as_ref().err };
if let Ok(io_state_lck) = io_state.0.lock() {
_ = io_state_lck.sender.try_send(());
if let Ok(mut done) = io_state_lck.done.lock() {
*done = Some(gpgme_error_try(&io_state_lck.lib, err));
}
}
return;
}
if r#type == gpgme_event_io_t::GPGME_EVENT_NEXT_KEY {
let Some(ptr) = NonNull::new(type_data.cast::<_gpgme_key>()) else {
log::error!(
"gpgme_event_io_cb NEXT_KEY event with NULL type_data. This is a gpgme bug.",
);
return;
};
if let Ok(io_state_lck) = io_state.0.lock() {
_ = io_state_lck.key_sender.try_send(KeyInner { ptr });
}
return;
}
log::error!(
"gpgme_event_io_cb called with unexpected event type: {}",
r#type as u32
);
}
impl Read for Data {
#[inline]
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let result = unsafe {
let (buf, len) = (buf.as_mut_ptr() as *mut _, buf.len());
call!(self.lib, gpgme_data_read)(
self.inner.as_ptr(),
buf,
len.try_into()
.map_err(|_| io::Error::from_raw_os_error(libc::EOVERFLOW))?,
)
};
if result >= 0 {
Ok(result as usize)
} else {
Err(io::Error::last_os_error())
}
}
}
impl Write for Data {
#[inline]
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
let result = unsafe {
let (buf, len) = (buf.as_ptr() as *const _, buf.len());
call!(self.lib, gpgme_data_write)(
self.inner.as_ptr(),
buf,
len.try_into()
.map_err(|_| io::Error::from_raw_os_error(libc::EOVERFLOW))?,
)
};
if result >= 0 {
Ok(result as usize)
} else {
Err(io::Error::last_os_error())
}
}
#[inline]
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
impl Seek for Data {
#[inline]
fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
let (off, whence) = match pos {
io::SeekFrom::Start(off) => (
off.try_into()
.map_err(|_| io::Error::from_raw_os_error(libc::EOVERFLOW))?,
libc::SEEK_SET,
),
io::SeekFrom::End(off) => (off.saturating_abs(), libc::SEEK_END),
io::SeekFrom::Current(off) => (off, libc::SEEK_CUR),
};
let result = unsafe {
#[allow(clippy::useless_conversion)]
call!(self.lib, gpgme_data_seek)(
self.inner.as_ptr(),
libc::off_t::try_from(off)
.map_err(|_| io::Error::from_raw_os_error(libc::EOVERFLOW))?
.into(),
whence,
)
};
if result >= 0 {
Ok(u64::try_from(result).map_err(|_| io::Error::from_raw_os_error(libc::EOVERFLOW))?)
} else {
Err(io::Error::last_os_error())
}
}
}