xpc: Fixes file handle explosion - drop fd after its copied via xpc
This commit is contained in:
@@ -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(|| "<unknown>".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);
|
||||
|
||||
@@ -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<Box<dyn Any + Send>>,
|
||||
}
|
||||
|
||||
impl DispatchResult {
|
||||
pub fn new(message: Message) -> Self {
|
||||
Self { message, cleanup: None }
|
||||
}
|
||||
|
||||
pub fn with_cleanup<T: Any + Send + 'static>(message: Message, cleanup: T) -> Self {
|
||||
Self { message, cleanup: Some(Box::new(cleanup)) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Vec<XpcConn>>,
|
||||
current_client: xpc_sys::xpc_connection_t,
|
||||
root: &HashMap<CString, Message>,
|
||||
) -> 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<String> = 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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user