diff --git a/kordophoned/src/xpc/agent.rs b/kordophoned/src/xpc/agent.rs index 9a7040b..6ebdf79 100644 --- a/kordophoned/src/xpc/agent.rs +++ b/kordophoned/src/xpc/agent.rs @@ -110,10 +110,10 @@ impl XpcAgent { Message::Dictionary(map) => { let method = super::util::dict_get_str(&map, "method").or_else(|| super::util::dict_get_str(&map, "type")).unwrap_or_else(|| "".to_string()); log::trace!(target: LOG_TARGET, "XPC request received: {}", method); - let response = rt_conn.block_on(super::rpc::dispatch(&agent_conn, &conns_for_handler, client, &map)); + let result = rt_conn.block_on(super::rpc::dispatch(&agent_conn, &conns_for_handler, client, &map)); let reply = xpc_sys::xpc_dictionary_create_reply(msg); if !reply.is_null() { - let payload = message_to_xpc_object(response); + let payload = message_to_xpc_object(result.message); let apply_block = ConcreteBlock::new(move |key: *const c_char, value: xpc_sys::xpc_object_t| { xpc_sys::xpc_dictionary_set_value(reply, key, value); }) @@ -124,6 +124,10 @@ impl XpcAgent { xpc_sys::xpc_connection_send_message(client, reply); xpc_sys::xpc_release(payload); xpc_sys::xpc_release(reply); + + // Drop any cleanup resource now that payload is constructed and sent. + drop(result.cleanup); + log::trace!(target: LOG_TARGET, "XPC reply sent for method: {}", method); } else { log::warn!(target: LOG_TARGET, "No reply port for method: {}", method); diff --git a/kordophoned/src/xpc/mod.rs b/kordophoned/src/xpc/mod.rs index 81cbe3f..d2bf926 100644 --- a/kordophoned/src/xpc/mod.rs +++ b/kordophoned/src/xpc/mod.rs @@ -2,3 +2,23 @@ pub mod agent; pub mod interface; pub mod rpc; pub mod util; + +use std::any::Any; +use xpc_connection::Message; + +/// Result of dispatching an XPC request: the message to send plus an optional +/// resource to keep alive until after the XPC payload is constructed. +pub struct DispatchResult { + pub message: Message, + pub cleanup: Option>, +} + +impl DispatchResult { + pub fn new(message: Message) -> Self { + Self { message, cleanup: None } + } + + pub fn with_cleanup(message: Message, cleanup: T) -> Self { + Self { message, cleanup: Some(Box::new(cleanup)) } + } +} diff --git a/kordophoned/src/xpc/rpc.rs b/kordophoned/src/xpc/rpc.rs index 1a599bf..0d8b5c3 100644 --- a/kordophoned/src/xpc/rpc.rs +++ b/kordophoned/src/xpc/rpc.rs @@ -7,22 +7,23 @@ use xpc_connection::Message; use xpc_connection_sys as xpc_sys; use super::util::*; +use super::DispatchResult; pub async fn dispatch( agent: &XpcAgent, subscribers: &std::sync::Mutex>, current_client: xpc_sys::xpc_connection_t, root: &HashMap, -) -> Message { +) -> DispatchResult { let request_id = dict_get_str(root, "request_id"); let method = match dict_get_str(root, "method").or_else(|| dict_get_str(root, "type")) { Some(m) => m, None => { - return attach_request_id( + return DispatchResult::new(attach_request_id( make_error_reply("InvalidRequest", "Missing method/type"), request_id, - ) + )) } }; @@ -35,9 +36,9 @@ pub async fn dispatch( let mut reply: XpcMap = HashMap::new(); dict_put_str(&mut reply, "type", "GetVersionResponse"); dict_put_str(&mut reply, "version", &version); - Message::Dictionary(reply) + DispatchResult::new(Message::Dictionary(reply)) } - Err(e) => make_error_reply("DaemonError", &format!("{}", e)), + Err(e) => DispatchResult::new(make_error_reply("DaemonError", &format!("{}", e))), }, // GetConversations @@ -84,34 +85,34 @@ pub async fn dispatch( let mut reply: XpcMap = HashMap::new(); dict_put_str(&mut reply, "type", "GetConversationsResponse"); reply.insert(cstr("conversations"), Message::Array(items)); - Message::Dictionary(reply) + DispatchResult::new(Message::Dictionary(reply)) } - Err(e) => make_error_reply("DaemonError", &format!("{}", e)), + Err(e) => DispatchResult::new(make_error_reply("DaemonError", &format!("{}", e))), } } // Sync ops "SyncConversationList" => match agent.send_event(Event::SyncConversationList).await { - Ok(()) => make_ok_reply(), - Err(e) => make_error_reply("DaemonError", &format!("{}", e)), + Ok(()) => DispatchResult::new(make_ok_reply()), + Err(e) => DispatchResult::new(make_error_reply("DaemonError", &format!("{}", e))), }, "SyncAllConversations" => match agent.send_event(Event::SyncAllConversations).await { - Ok(()) => make_ok_reply(), - Err(e) => make_error_reply("DaemonError", &format!("{}", e)), + Ok(()) => DispatchResult::new(make_ok_reply()), + Err(e) => DispatchResult::new(make_error_reply("DaemonError", &format!("{}", e))), }, "SyncConversation" => { let conversation_id = match get_dictionary_field(root, "arguments") .and_then(|m| dict_get_str(m, "conversation_id")) { Some(id) => id, - None => return make_error_reply("InvalidRequest", "Missing conversation_id"), + None => return DispatchResult::new(make_error_reply("InvalidRequest", "Missing conversation_id")), }; match agent .send_event(|r| Event::SyncConversation(conversation_id, r)) .await { - Ok(()) => make_ok_reply(), - Err(e) => make_error_reply("DaemonError", &format!("{}", e)), + Ok(()) => DispatchResult::new(make_ok_reply()), + Err(e) => DispatchResult::new(make_error_reply("DaemonError", &format!("{}", e))), } } @@ -121,14 +122,14 @@ pub async fn dispatch( .and_then(|m| dict_get_str(m, "conversation_id")) { Some(id) => id, - None => return make_error_reply("InvalidRequest", "Missing conversation_id"), + None => return DispatchResult::new(make_error_reply("InvalidRequest", "Missing conversation_id")), }; match agent .send_event(|r| Event::MarkConversationAsRead(conversation_id, r)) .await { - Ok(()) => make_ok_reply(), - Err(e) => make_error_reply("DaemonError", &format!("{}", e)), + Ok(()) => DispatchResult::new(make_ok_reply()), + Err(e) => DispatchResult::new(make_error_reply("DaemonError", &format!("{}", e))), } } @@ -136,11 +137,11 @@ pub async fn dispatch( "GetMessages" => { let args = match get_dictionary_field(root, "arguments") { Some(a) => a, - None => return make_error_reply("InvalidRequest", "Missing arguments"), + None => return DispatchResult::new(make_error_reply("InvalidRequest", "Missing arguments")), }; let conversation_id = match dict_get_str(args, "conversation_id") { Some(id) => id, - None => return make_error_reply("InvalidRequest", "Missing conversation_id"), + None => return DispatchResult::new(make_error_reply("InvalidRequest", "Missing conversation_id")), }; let last_message_id = dict_get_str(args, "last_message_id"); match agent @@ -213,27 +214,27 @@ pub async fn dispatch( let mut reply: XpcMap = HashMap::new(); dict_put_str(&mut reply, "type", "GetMessagesResponse"); reply.insert(cstr("messages"), Message::Array(items)); - Message::Dictionary(reply) + DispatchResult::new(Message::Dictionary(reply)) } - Err(e) => make_error_reply("DaemonError", &format!("{}", e)), + Err(e) => DispatchResult::new(make_error_reply("DaemonError", &format!("{}", e))), } } // Delete all "DeleteAllConversations" => match agent.send_event(Event::DeleteAllConversations).await { - Ok(()) => make_ok_reply(), - Err(e) => make_error_reply("DaemonError", &format!("{}", e)), + Ok(()) => DispatchResult::new(make_ok_reply()), + Err(e) => DispatchResult::new(make_error_reply("DaemonError", &format!("{}", e))), }, // SendMessage "SendMessage" => { let args = match get_dictionary_field(root, "arguments") { Some(a) => a, - None => return make_error_reply("InvalidRequest", "Missing arguments"), + None => return DispatchResult::new(make_error_reply("InvalidRequest", "Missing arguments")), }; let conversation_id = match dict_get_str(args, "conversation_id") { Some(v) => v, - None => return make_error_reply("InvalidRequest", "Missing conversation_id"), + None => return DispatchResult::new(make_error_reply("InvalidRequest", "Missing conversation_id")), }; let text = dict_get_str(args, "text").unwrap_or_default(); let attachment_guids: Vec = match args.get(&cstr("attachment_guids")) { @@ -254,9 +255,9 @@ pub async fn dispatch( let mut reply: XpcMap = HashMap::new(); dict_put_str(&mut reply, "type", "SendMessageResponse"); dict_put_str(&mut reply, "uuid", &uuid.to_string()); - Message::Dictionary(reply) + DispatchResult::new(Message::Dictionary(reply)) } - Err(e) => make_error_reply("DaemonError", &format!("{}", e)), + Err(e) => DispatchResult::new(make_error_reply("DaemonError", &format!("{}", e))), } } @@ -264,11 +265,11 @@ pub async fn dispatch( "GetAttachmentInfo" => { let args = match get_dictionary_field(root, "arguments") { Some(a) => a, - None => return make_error_reply("InvalidRequest", "Missing arguments"), + None => return DispatchResult::new(make_error_reply("InvalidRequest", "Missing arguments")), }; let attachment_id = match dict_get_str(args, "attachment_id") { Some(v) => v, - None => return make_error_reply("InvalidRequest", "Missing attachment_id"), + None => return DispatchResult::new(make_error_reply("InvalidRequest", "Missing attachment_id")), }; match agent .send_event(|r| Event::GetAttachment(attachment_id, r)) @@ -297,9 +298,9 @@ pub async fn dispatch( "preview_downloaded", &attachment.is_downloaded(true).to_string(), ); - Message::Dictionary(reply) + DispatchResult::new(Message::Dictionary(reply)) } - Err(e) => make_error_reply("DaemonError", &format!("{}", e)), + Err(e) => DispatchResult::new(make_error_reply("DaemonError", &format!("{}", e))), } } @@ -307,11 +308,11 @@ pub async fn dispatch( "OpenAttachmentFd" => { let args = match get_dictionary_field(root, "arguments") { Some(a) => a, - None => return make_error_reply("InvalidRequest", "Missing arguments"), + None => return DispatchResult::new(make_error_reply("InvalidRequest", "Missing arguments")), }; let attachment_id = match dict_get_str(args, "attachment_id") { Some(v) => v, - None => return make_error_reply("InvalidRequest", "Missing attachment_id"), + None => return DispatchResult::new(make_error_reply("InvalidRequest", "Missing attachment_id")), }; let preview = dict_get_str(args, "preview") .map(|s| s == "true") @@ -327,22 +328,19 @@ pub async fn dispatch( let path = attachment.get_path_for_preview(preview); match std::fs::OpenOptions::new().read(true).open(&path) { Ok(file) => { + use std::os::fd::AsRawFd; let fd = file.as_raw_fd(); - // Keep file alive until after conversion to XPC - std::mem::forget(file); - - // Return file descriptor in reply let mut reply: XpcMap = HashMap::new(); dict_put_str(&mut reply, "type", "OpenAttachmentFdResponse"); reply.insert(cstr("fd"), Message::Fd(fd)); - Message::Dictionary(reply) + DispatchResult { message: Message::Dictionary(reply), cleanup: Some(Box::new(file)) } } - Err(e) => make_error_reply("OpenFailed", &format!("{}", e)), + Err(e) => DispatchResult::new(make_error_reply("OpenFailed", &format!("{}", e))), } } - Err(e) => make_error_reply("DaemonError", &format!("{}", e)), + Err(e) => DispatchResult::new(make_error_reply("DaemonError", &format!("{}", e))), } } @@ -350,11 +348,11 @@ pub async fn dispatch( "DownloadAttachment" => { let args = match get_dictionary_field(root, "arguments") { Some(a) => a, - None => return make_error_reply("InvalidRequest", "Missing arguments"), + None => return DispatchResult::new(make_error_reply("InvalidRequest", "Missing arguments")), }; let attachment_id = match dict_get_str(args, "attachment_id") { Some(v) => v, - None => return make_error_reply("InvalidRequest", "Missing attachment_id"), + None => return DispatchResult::new(make_error_reply("InvalidRequest", "Missing attachment_id")), }; let preview = dict_get_str(args, "preview") .map(|s| s == "true") @@ -363,8 +361,8 @@ pub async fn dispatch( .send_event(|r| Event::DownloadAttachment(attachment_id, preview, r)) .await { - Ok(()) => make_ok_reply(), - Err(e) => make_error_reply("DaemonError", &format!("{}", e)), + Ok(()) => DispatchResult::new(make_ok_reply()), + Err(e) => DispatchResult::new(make_error_reply("DaemonError", &format!("{}", e))), } } @@ -373,11 +371,11 @@ pub async fn dispatch( use std::path::PathBuf; let args = match get_dictionary_field(root, "arguments") { Some(a) => a, - None => return make_error_reply("InvalidRequest", "Missing arguments"), + None => return DispatchResult::new(make_error_reply("InvalidRequest", "Missing arguments")), }; let path = match dict_get_str(args, "path") { Some(v) => v, - None => return make_error_reply("InvalidRequest", "Missing path"), + None => return DispatchResult::new(make_error_reply("InvalidRequest", "Missing path")), }; match agent .send_event(|r| Event::UploadAttachment(PathBuf::from(path), r)) @@ -387,9 +385,9 @@ pub async fn dispatch( let mut reply: XpcMap = HashMap::new(); dict_put_str(&mut reply, "type", "UploadAttachmentResponse"); dict_put_str(&mut reply, "upload_guid", &upload_guid); - Message::Dictionary(reply) + DispatchResult::new(Message::Dictionary(reply)) } - Err(e) => make_error_reply("DaemonError", &format!("{}", e)), + Err(e) => DispatchResult::new(make_error_reply("DaemonError", &format!("{}", e))), } } @@ -408,14 +406,14 @@ pub async fn dispatch( "username", &settings.username.unwrap_or_default(), ); - Message::Dictionary(reply) + DispatchResult::new(Message::Dictionary(reply)) } - Err(e) => make_error_reply("DaemonError", &format!("{}", e)), + Err(e) => DispatchResult::new(make_error_reply("DaemonError", &format!("{}", e))), }, "UpdateSettings" => { let args = match get_dictionary_field(root, "arguments") { Some(a) => a, - None => return make_error_reply("InvalidRequest", "Missing arguments"), + None => return DispatchResult::new(make_error_reply("InvalidRequest", "Missing arguments")), }; let server_url = dict_get_str(args, "server_url"); let username = dict_get_str(args, "username"); @@ -428,8 +426,8 @@ pub async fn dispatch( .send_event(|r| Event::UpdateSettings(settings, r)) .await { - Ok(()) => make_ok_reply(), - Err(e) => make_error_reply("DaemonError", &format!("{}", e)), + Ok(()) => DispatchResult::new(make_ok_reply()), + Err(e) => DispatchResult::new(make_error_reply("DaemonError", &format!("{}", e))), } } @@ -440,13 +438,13 @@ pub async fn dispatch( list.push(XpcConn(current_client)); log::trace!(target: LOG_TARGET, "Client subscribed to signals (total subscribers: {})", list.len()); } - make_ok_reply() + DispatchResult::new(make_ok_reply()) } // Unknown method fallback - other => make_error_reply("UnknownMethod", other), + other => DispatchResult::new(make_error_reply("UnknownMethod", other)), }; - response = attach_request_id(response, request_id); + response.message = attach_request_id(response.message, request_id); response }