melib/notmuch: wrap *mut struct fields in NonNull<_>

Signed-off-by: Manos Pitsidianakis <manos@pitsidianak.is>
pull/329/head
Manos Pitsidianakis 2023-12-17 18:47:37 +02:00
parent 506ae9f594
commit ebe1b3da7e
Signed by: Manos Pitsidianakis
GPG Key ID: 7729C7707F7E09D0
5 changed files with 200 additions and 118 deletions

View File

@ -19,6 +19,8 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
use std::{marker::PhantomData, ptr::NonNull};
use super::*;
use crate::{
notmuch::ffi::{
@ -33,10 +35,10 @@ use crate::{
#[derive(Clone)]
pub struct Message<'m> {
pub lib: Arc<libloading::Library>,
pub message: *mut ffi::notmuch_message_t,
pub lib: Arc<NotmuchLibrary>,
pub message: NonNull<ffi::notmuch_message_t>,
pub is_from_thread: bool,
pub _ph: std::marker::PhantomData<&'m ffi::notmuch_message_t>,
pub _ph: PhantomData<&'m ffi::notmuch_message_t>,
}
impl<'m> Message<'m> {
@ -50,29 +52,30 @@ impl<'m> Message<'m> {
std::ptr::addr_of_mut!(message),
)
};
if message.is_null() {
return Err(Error::new(format!(
"Message with message id {:?} not found in notmuch database.",
msg_id
)));
}
Ok(Message {
lib,
message,
message: NonNull::new(message).ok_or_else(|| {
Error::new(format!(
"Message with message id {:?} not found in notmuch database.",
msg_id
))
})?,
is_from_thread: false,
_ph: std::marker::PhantomData,
_ph: PhantomData,
})
}
pub fn env_hash(&self) -> EnvelopeHash {
let msg_id = unsafe { call!(self.lib, notmuch_message_get_message_id)(self.message) };
let msg_id =
unsafe { call!(self.lib, notmuch_message_get_message_id)(self.message.as_ptr()) };
let c_str = unsafe { CStr::from_ptr(msg_id) };
EnvelopeHash::from_bytes(c_str.to_bytes_with_nul())
}
pub fn header(&self, header: &CStr) -> Option<&[u8]> {
let header_val =
unsafe { call!(self.lib, notmuch_message_get_header)(self.message, header.as_ptr()) };
let header_val = unsafe {
call!(self.lib, notmuch_message_get_header)(self.message.as_ptr(), header.as_ptr())
};
if header_val.is_null() {
None
} else {
@ -86,12 +89,13 @@ impl<'m> Message<'m> {
}
pub fn msg_id_cstr(&self) -> &CStr {
let msg_id = unsafe { call!(self.lib, notmuch_message_get_message_id)(self.message) };
let msg_id =
unsafe { call!(self.lib, notmuch_message_get_message_id)(self.message.as_ptr()) };
unsafe { CStr::from_ptr(msg_id) }
}
pub fn date(&self) -> crate::UnixTimestamp {
(unsafe { call!(self.lib, notmuch_message_get_date)(self.message) }) as u64
(unsafe { call!(self.lib, notmuch_message_get_date)(self.message.as_ptr()) }) as u64
}
pub fn into_envelope(
@ -173,23 +177,22 @@ impl<'m> Message<'m> {
pub fn replies_iter(&self) -> Option<MessageIterator> {
if self.is_from_thread {
let messages = unsafe { call!(self.lib, notmuch_message_get_replies)(self.message) };
if messages.is_null() {
None
} else {
Some(MessageIterator {
lib: self.lib.clone(),
messages,
_ph: std::marker::PhantomData,
is_from_thread: true,
})
}
let messages = Some(NonNull::new(unsafe {
call!(self.lib, notmuch_message_get_replies)(self.message.as_ptr())
})?);
Some(MessageIterator {
lib: self.lib.clone(),
messages,
_ph: PhantomData,
is_from_thread: true,
})
} else {
None
}
}
pub fn into_thread_node(&self) -> (ThreadNodeHash, ThreadNode) {
let (flags, _) = TagIterator::new(self).collect_flags_and_tags();
(
ThreadNodeHash::from(self.msg_id()),
ThreadNode {
@ -200,7 +203,7 @@ impl<'m> Message<'m> {
date: self.date(),
show_subject: true,
group: ThreadHash::new(),
unseen: false,
unseen: !flags.intersects(Flag::SEEN),
},
)
}
@ -209,7 +212,7 @@ impl<'m> Message<'m> {
if let Err(err) = unsafe {
try_call!(
self.lib,
call!(self.lib, notmuch_message_add_tag)(self.message, tag.as_ptr())
call!(self.lib, notmuch_message_add_tag)(self.message.as_ptr(), tag.as_ptr())
)
} {
return Err(Error::new("Could not set tag.").set_source(Some(Arc::new(err))));
@ -221,7 +224,7 @@ impl<'m> Message<'m> {
if let Err(err) = unsafe {
try_call!(
self.lib,
call!(self.lib, notmuch_message_remove_tag)(self.message, tag.as_ptr())
call!(self.lib, notmuch_message_remove_tag)(self.message.as_ptr(), tag.as_ptr())
)
} {
return Err(Error::new("Could not set tag.").set_source(Some(Arc::new(err))));
@ -237,7 +240,7 @@ impl<'m> Message<'m> {
if let Err(err) = unsafe {
try_call!(
self.lib,
call!(self.lib, notmuch_message_tags_to_maildir_flags)(self.message)
call!(self.lib, notmuch_message_tags_to_maildir_flags)(self.message.as_ptr())
)
} {
return Err(Error::new("Could not set flags.").set_source(Some(Arc::new(err))));
@ -246,7 +249,8 @@ impl<'m> Message<'m> {
}
pub fn get_filename(&self) -> &OsStr {
let fs_path = unsafe { call!(self.lib, notmuch_message_get_filename)(self.message) };
let fs_path =
unsafe { call!(self.lib, notmuch_message_get_filename)(self.message.as_ptr()) };
let c_str = unsafe { CStr::from_ptr(fs_path) };
OsStr::from_bytes(c_str.to_bytes())
}
@ -254,35 +258,35 @@ impl<'m> Message<'m> {
impl Drop for Message<'_> {
fn drop(&mut self) {
unsafe { call!(self.lib, notmuch_message_destroy)(self.message) };
unsafe { call!(self.lib, notmuch_message_destroy)(self.message.as_ptr()) };
}
}
pub struct MessageIterator<'query> {
pub lib: Arc<libloading::Library>,
pub messages: *mut ffi::notmuch_messages_t,
pub lib: Arc<NotmuchLibrary>,
pub messages: Option<NonNull<ffi::notmuch_messages_t>>,
pub is_from_thread: bool,
pub _ph: std::marker::PhantomData<*const Query<'query>>,
pub _ph: PhantomData<*const Query<'query>>,
}
impl<'q> Iterator for MessageIterator<'q> {
type Item = Message<'q>;
fn next(&mut self) -> Option<Self::Item> {
if self.messages.is_null() {
None
} else if unsafe { call!(self.lib, notmuch_messages_valid)(self.messages) } == 1 {
let message = unsafe { call!(self.lib, notmuch_messages_get)(self.messages) };
let messages = self.messages?;
if unsafe { call!(self.lib, notmuch_messages_valid)(messages.as_ptr()) } == 1 {
let message = unsafe { call!(self.lib, notmuch_messages_get)(messages.as_ptr()) };
unsafe {
call!(self.lib, notmuch_messages_move_to_next)(self.messages);
call!(self.lib, notmuch_messages_move_to_next)(messages.as_ptr());
}
Some(Message {
lib: self.lib.clone(),
message,
message: NonNull::new(message)?,
is_from_thread: self.is_from_thread,
_ph: std::marker::PhantomData,
_ph: PhantomData,
})
} else {
self.messages = std::ptr::null_mut();
self.messages = None;
None
}
}

View File

@ -20,6 +20,7 @@
*/
use std::{
borrow::Cow,
collections::{hash_map::HashMap, BTreeMap, BTreeSet},
ffi::{CStr, CString, OsStr},
io::Read,
@ -50,7 +51,7 @@ macro_rules! call {
"{} must be a valid FFI symbol.",
stringify!($func)
);
let func: libloading::Symbol<$func> = $lib.get(stringify!($func).as_bytes()).unwrap();
let func: libloading::Symbol<$func> = $lib.inner.get(stringify!($func).as_bytes()).unwrap();
func
}};
}
@ -101,9 +102,15 @@ impl DbPointer {
}
}
#[derive(Debug)]
pub struct NotmuchLibrary {
pub inner: libloading::Library,
pub dlpath: Cow<'static, str>,
}
#[derive(Debug)]
pub struct DbConnection {
pub lib: Arc<libloading::Library>,
pub lib: Arc<NotmuchLibrary>,
inner: Arc<Mutex<DbPointer>>,
pub revision_uuid: Arc<RwLock<u64>>,
}
@ -201,10 +208,9 @@ impl DbConnection {
);
}
}
false
} else {
true
return false;
}
true
});
Ok(())
}
@ -249,7 +255,7 @@ impl Drop for DbConnection {
#[derive(Debug)]
pub struct NotmuchDb {
#[allow(dead_code)]
lib: Arc<libloading::Library>,
lib: Arc<NotmuchLibrary>,
revision_uuid: Arc<RwLock<u64>>,
mailboxes: Arc<RwLock<HashMap<MailboxHash, NotmuchMailbox>>>,
index: Arc<RwLock<HashMap<EnvelopeHash, CString>>>,
@ -270,35 +276,38 @@ impl NotmuchDb {
event_consumer: BackendEventConsumer,
) -> Result<Box<dyn MailBackend>> {
#[cfg(target_os = "linux")]
let mut dlpath = "libnotmuch.so.5";
let mut dlpath = Cow::Borrowed("libnotmuch.so.5");
#[cfg(target_os = "macos")]
let mut dlpath = "libnotmuch.5.dylib";
let mut dlpath = Cow::Borrowed("libnotmuch.5.dylib");
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
let mut dlpath = "libnotmuch.so";
let mut dlpath = Cow::Borrowed("libnotmuch.so");
let mut custom_dlpath = false;
if let Some(lib_path) = s.extra.get("library_file_path") {
dlpath = lib_path.as_str();
dlpath = Cow::Owned(lib_path.to_string());
custom_dlpath = true;
}
let lib = Arc::new(unsafe {
match libloading::Library::new(dlpath) {
Ok(l) => l,
Err(err) => {
if custom_dlpath {
return Err(Error::new(format!(
"Notmuch `library_file_path` setting value `{}` for account {} does \
not exist or is a directory or not a valid library file.",
dlpath, s.name
))
.set_kind(ErrorKind::Configuration)
.set_source(Some(Arc::new(err))));
} else {
return Err(Error::new("Could not load libnotmuch!")
.set_details(super::NOTMUCH_ERROR_DETAILS)
let lib = Arc::new(NotmuchLibrary {
inner: unsafe {
match libloading::Library::new(dlpath.as_ref()) {
Ok(l) => l,
Err(err) => {
if custom_dlpath {
return Err(Error::new(format!(
"Notmuch `library_file_path` setting value `{}` for account {} \
does not exist or is a directory or not a valid library file.",
dlpath, s.name
))
.set_kind(ErrorKind::Configuration)
.set_source(Some(Arc::new(err))));
} else {
return Err(Error::new("Could not load libnotmuch!")
.set_details(super::NOTMUCH_ERROR_DETAILS)
.set_source(Some(Arc::new(err))));
}
}
}
}
},
dlpath,
});
let mut path = Path::new(s.root_mailbox.as_str()).expand();
if !path.exists() {
@ -486,7 +495,7 @@ impl NotmuchDb {
fn new_connection(
path: &Path,
revision_uuid: Arc<RwLock<u64>>,
lib: Arc<libloading::Library>,
lib: Arc<NotmuchLibrary>,
write: bool,
) -> Result<DbConnection> {
let path_c = CString::new(path.to_str().unwrap()).unwrap();
@ -1030,7 +1039,7 @@ struct NotmuchOp {
database: Arc<DbConnection>,
bytes: Option<Vec<u8>>,
#[allow(dead_code)]
lib: Arc<libloading::Library>,
lib: Arc<NotmuchLibrary>,
}
impl BackendOp for NotmuchOp {

View File

@ -19,30 +19,31 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
use std::{ffi::CString, ptr::NonNull, sync::Arc};
use std::{borrow::Cow, ffi::CString, ptr::NonNull, sync::Arc};
use crate::{
error::{Error, Result},
error::{Error, ErrorKind, Result},
notmuch::{
ffi::{
self, notmuch_query_count_messages, notmuch_query_create, notmuch_query_destroy,
notmuch_query_search_messages, notmuch_status_to_string,
notmuch_messages_t, notmuch_query_count_messages, notmuch_query_create,
notmuch_query_destroy, notmuch_query_search_messages, notmuch_query_t,
notmuch_status_to_string,
},
DbConnection, MessageIterator,
DbConnection, MessageIterator, NotmuchLibrary,
},
};
pub struct Query<'s> {
pub lib: Arc<libloading::Library>,
pub ptr: NonNull<ffi::notmuch_query_t>,
pub lib: Arc<NotmuchLibrary>,
pub ptr: NonNull<notmuch_query_t>,
pub query_str: &'s str,
}
impl<'s> Query<'s> {
pub fn new(database: &DbConnection, query_str: &'s str) -> Result<Self> {
let lib: Arc<libloading::Library> = database.lib.clone();
let lib: Arc<NotmuchLibrary> = database.lib.clone();
let query_cstr = CString::new(query_str)?;
let query: *mut ffi::notmuch_query_t = unsafe {
let query: *mut notmuch_query_t = unsafe {
call!(lib, notmuch_query_create)(
database.inner.lock().unwrap().as_mut(),
query_cstr.as_ptr(),
@ -72,7 +73,7 @@ impl<'s> Query<'s> {
}
pub fn search(&'s self) -> Result<MessageIterator<'s>> {
let mut messages: *mut ffi::notmuch_messages_t = std::ptr::null_mut();
let mut messages: *mut notmuch_messages_t = std::ptr::null_mut();
let status = unsafe {
call!(self.lib, notmuch_query_search_messages)(
self.ptr.as_ptr(),
@ -85,12 +86,25 @@ impl<'s> Query<'s> {
self.query_str, status,
)));
}
assert!(!messages.is_null());
let messages = Some(NonNull::new(messages).ok_or_else(|| {
Error::new(format!(
"Search for {} failed because of an internal libnotmuch error.",
self.query_str
))
.set_details(
"notmuch_query_search_messages returned status == 0 but the passed `messages` \
pointer argument is NULL.",
)
.set_kind(ErrorKind::LinkedLibrary(match self.lib.dlpath {
Cow::Borrowed(v) => v,
Cow::Owned(_) => "user configured path",
}))
})?);
Ok(MessageIterator {
messages,
lib: self.lib.clone(),
_ph: std::marker::PhantomData,
is_from_thread: false,
_ph: std::marker::PhantomData,
})
}
}

View File

@ -19,33 +19,40 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
use std::ptr::NonNull;
use super::*;
use crate::notmuch::ffi::{
notmuch_message_get_filename, notmuch_message_get_tags, notmuch_tags_destroy, notmuch_tags_get,
notmuch_tags_move_to_next, notmuch_tags_valid,
notmuch_tags_move_to_next, notmuch_tags_t, notmuch_tags_valid,
};
pub struct TagIterator<'m> {
pub tags: *mut ffi::notmuch_tags_t,
pub tags: Option<NonNull<notmuch_tags_t>>,
pub message: &'m Message<'m>,
}
impl Drop for TagIterator<'_> {
fn drop(&mut self) {
unsafe { call!(self.message.lib, notmuch_tags_destroy)(self.tags) };
if let Some(tags) = self.tags {
unsafe { call!(self.message.lib, notmuch_tags_destroy)(tags.as_ptr()) };
}
}
}
impl<'m> TagIterator<'m> {
pub fn new(message: &'m Message<'m>) -> TagIterator<'m> {
TagIterator {
tags: unsafe { call!(message.lib, notmuch_message_get_tags)(message.message) },
tags: NonNull::new(unsafe {
call!(message.lib, notmuch_message_get_tags)(message.message.as_ptr())
}),
message,
}
}
pub fn collect_flags_and_tags(self) -> (Flag, Vec<String>) {
fn flags(path: &CStr) -> Flag {
fn flags(fs_path: NonNull<std::ffi::c_char>) -> Flag {
let path = unsafe { CStr::from_ptr(fs_path.as_ptr()) };
let mut flag = Flag::default();
let mut ptr = path.to_bytes().len().saturating_sub(1);
let mut is_valid = true;
@ -75,9 +82,12 @@ impl<'m> TagIterator<'m> {
flag
}
let fs_path =
unsafe { call!(self.message.lib, notmuch_message_get_filename)(self.message.message) };
let c_str = unsafe { CStr::from_ptr(fs_path) };
let fs_path = unsafe {
// SAFETY;
// all used pointers here are NonNull<wrapped>, and the cast to *mut _
// afterwards is only to wrap the retval into a NonNull as well.
call!(self.message.lib, notmuch_message_get_filename)(self.message.message.as_ptr())
} as *mut std::ffi::c_char;
let tags = self.collect::<Vec<&CStr>>();
let mut flag = Flag::default();
@ -108,25 +118,28 @@ impl<'m> TagIterator<'m> {
}
}
(flag | flags(c_str), vec)
(
flag | NonNull::new(fs_path).map(flags).unwrap_or_default(),
vec,
)
}
}
impl<'m> Iterator for TagIterator<'m> {
type Item = &'m CStr;
fn next(&mut self) -> Option<Self::Item> {
if self.tags.is_null() {
None
} else if unsafe { call!(self.message.lib, notmuch_tags_valid)(self.tags) } == 1 {
let tags = self.tags?;
if unsafe { call!(self.message.lib, notmuch_tags_valid)(tags.as_ptr()) } == 1 {
let ret = Some(unsafe {
CStr::from_ptr(call!(self.message.lib, notmuch_tags_get)(self.tags))
CStr::from_ptr(call!(self.message.lib, notmuch_tags_get)(tags.as_ptr()))
});
unsafe {
call!(self.message.lib, notmuch_tags_move_to_next)(self.tags);
call!(self.message.lib, notmuch_tags_move_to_next)(tags.as_ptr());
}
ret
} else {
self.tags = std::ptr::null_mut();
unsafe { call!(self.message.lib, notmuch_tags_destroy)(tags.as_ptr()) };
self.tags = None;
None
}
}

View File

@ -22,42 +22,63 @@ use super::*;
use crate::{
notmuch::ffi::{
notmuch_thread_destroy, notmuch_thread_get_messages, notmuch_thread_get_newest_date,
notmuch_thread_get_thread_id, notmuch_thread_get_total_messages, notmuch_threads_get,
notmuch_threads_move_to_next, notmuch_threads_valid,
notmuch_thread_get_thread_id, notmuch_thread_get_total_messages, notmuch_thread_t,
notmuch_threads_get, notmuch_threads_move_to_next, notmuch_threads_t,
notmuch_threads_valid,
},
thread::ThreadHash,
};
pub struct Thread<'query> {
pub lib: Arc<libloading::Library>,
pub ptr: *mut ffi::notmuch_thread_t,
pub lib: Arc<NotmuchLibrary>,
pub inner: NonNull<notmuch_thread_t>,
pub _ph: std::marker::PhantomData<*const Query<'query>>,
}
impl<'q> Thread<'q> {
#[inline]
pub fn id(&self) -> ThreadHash {
let thread_id = unsafe { call!(self.lib, notmuch_thread_get_thread_id)(self.ptr) };
let thread_id = unsafe {
// SAFETY:
// All pointers used here are NonNull<_> wrapped.
call!(self.lib, notmuch_thread_get_thread_id)(self.inner.as_ptr())
};
let c_str = unsafe { CStr::from_ptr(thread_id) };
ThreadHash::from(c_str.to_bytes())
}
#[inline]
pub fn date(&self) -> crate::UnixTimestamp {
(unsafe { call!(self.lib, notmuch_thread_get_newest_date)(self.ptr) }) as u64
(unsafe {
// SAFETY:
// All pointers used here are NonNull<_> wrapped.
call!(self.lib, notmuch_thread_get_newest_date)(self.inner.as_ptr())
}) as u64
}
#[inline]
pub fn len(&self) -> usize {
(unsafe { call!(self.lib, notmuch_thread_get_total_messages)(self.ptr) }) as usize
(unsafe {
// SAFETY:
// All pointers used here are NonNull<_> wrapped.
call!(self.lib, notmuch_thread_get_total_messages)(self.inner.as_ptr())
}) as usize
}
#[inline]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn iter(&'q self) -> MessageIterator<'q> {
let ptr = unsafe { call!(self.lib, notmuch_thread_get_messages)(self.ptr) };
let messages = NonNull::new(unsafe {
// SAFETY:
// All pointers used here are NonNull<_> wrapped.
call!(self.lib, notmuch_thread_get_messages)(self.inner.as_ptr())
});
MessageIterator {
lib: self.lib.clone(),
messages: ptr,
messages,
is_from_thread: true,
_ph: std::marker::PhantomData,
}
@ -66,33 +87,54 @@ impl<'q> Thread<'q> {
impl Drop for Thread<'_> {
fn drop(&mut self) {
unsafe { call!(self.lib, notmuch_thread_destroy)(self.ptr) }
unsafe {
// SAFETY:
// All pointers used here are NonNull<_> wrapped.
call!(self.lib, notmuch_thread_destroy)(self.inner.as_ptr())
}
}
}
/// notmuch threads iterator.
///
///
/// Quoting the docs:
///
/// > Note that there's no explicit destructor needed for the
/// > notmuch_threads_t object. (For consistency, we do provide a
/// > notmuch_threads_destroy function, but there's no good reason
/// > to call it if the query is about to be destroyed).
///
/// So there's no need to implement Drop for this type.
pub struct ThreadsIterator<'query> {
pub lib: Arc<libloading::Library>,
pub threads: *mut ffi::notmuch_threads_t,
pub lib: Arc<NotmuchLibrary>,
pub inner: Option<NonNull<notmuch_threads_t>>,
pub _ph: std::marker::PhantomData<*const Query<'query>>,
}
impl<'q> Iterator for ThreadsIterator<'q> {
type Item = Thread<'q>;
fn next(&mut self) -> Option<Self::Item> {
if self.threads.is_null() {
None
} else if unsafe { call!(self.lib, notmuch_threads_valid)(self.threads) } == 1 {
let thread = unsafe { call!(self.lib, notmuch_threads_get)(self.threads) };
let inner = self.inner?;
if unsafe { call!(self.lib, notmuch_threads_valid)(inner.as_ptr()) } == 1 {
let Some(thread_inner) = NonNull::new(unsafe {
// SAFETY:
// All pointers used here are NonNull<_> wrapped.
call!(self.lib, notmuch_threads_get)(inner.as_ptr())
}) else {
self.inner = None;
return None;
};
unsafe {
call!(self.lib, notmuch_threads_move_to_next)(self.threads);
call!(self.lib, notmuch_threads_move_to_next)(inner.as_ptr());
}
Some(Thread {
lib: self.lib.clone(),
ptr: thread,
inner: thread_inner,
_ph: std::marker::PhantomData,
})
} else {
self.threads = std::ptr::null_mut();
self.inner = None;
None
}
}