icecat: add release icecat-140.7.0-1gnu1 for ecne
This commit is contained in:
parent
7d0f5dab3b
commit
30225f2e73
156 changed files with 9131 additions and 4525 deletions
|
|
@ -607,7 +607,7 @@ export class FormAutofillHandler {
|
|||
|
||||
this.#refillTimeoutId = lazy.setTimeout(() => {
|
||||
for (let [e, v] of filledElementValues) {
|
||||
if (e.autofillState == FIELD_STATES.AUTO_FILLED && e.value === v) {
|
||||
if (e.autofillState == FIELD_STATES.NORMAL || e.value) {
|
||||
// Nothing to do if the autofilled value wasn't cleared or the
|
||||
// element's autofill state has changed to NORMAL in the meantime
|
||||
continue;
|
||||
|
|
|
|||
|
|
@ -22,7 +22,8 @@ namespace mozilla::glean {
|
|||
|
||||
namespace impl {
|
||||
|
||||
using CallbackMapType = nsTHashMap<uint32_t, FalliblePingTestCallback>;
|
||||
using CallbackMapType =
|
||||
nsTHashMap<NoMemMoveKey<nsUint32HashKey>, FalliblePingTestCallback>;
|
||||
using MetricIdToCallbackMutex = StaticDataMutex<UniquePtr<CallbackMapType>>;
|
||||
static Maybe<MetricIdToCallbackMutex::AutoLock> GetCallbackMapLock() {
|
||||
static MetricIdToCallbackMutex sCallbacks("sCallbacks");
|
||||
|
|
|
|||
|
|
@ -1203,6 +1203,9 @@ PdfStreamConverter.prototype = {
|
|||
);
|
||||
// The viewer does not need to handle HTTP Refresh header.
|
||||
aRequest.setResponseHeader("Refresh", "", false);
|
||||
// There is no reason to load something via <link>: the only external
|
||||
// resource is the pdf itself.
|
||||
aRequest.setResponseHeader("Link", "", false);
|
||||
}
|
||||
|
||||
lazy.PdfJsTelemetryContent.onViewerIsUsed();
|
||||
|
|
|
|||
|
|
@ -83,6 +83,9 @@ support-files = [
|
|||
|
||||
["browser_pdfjs_properties.js"]
|
||||
|
||||
["browser_pdfjs_response_link.js"]
|
||||
support-files = ["pdf_response_link.sjs"]
|
||||
|
||||
["browser_pdfjs_saveas.js"]
|
||||
support-files = [
|
||||
"!/toolkit/content/tests/browser/common/mockTransfer.js",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,46 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
const RELATIVE_DIR = "toolkit/components/pdfjs/test/";
|
||||
const TESTROOT = "https://example.com/browser/" + RELATIVE_DIR;
|
||||
|
||||
function getBodyBackgroundColor(browser) {
|
||||
return SpecialPowers.spawn(browser, [], async () => {
|
||||
return content.getComputedStyle(content.document.querySelector("body"))
|
||||
.backgroundColor;
|
||||
});
|
||||
}
|
||||
|
||||
// Sanity check: the pdf test does not trivially pass due to the lack of support
|
||||
// for Link header.
|
||||
add_task(async function test_plain_text_with_link_in_response() {
|
||||
await BrowserTestUtils.withNewTab(
|
||||
{ gBrowser, url: `${TESTROOT}pdf_response_link.sjs?text` },
|
||||
async function (browser) {
|
||||
const bodyBackgroundColor = await getBodyBackgroundColor(browser);
|
||||
Assert.equal(
|
||||
bodyBackgroundColor,
|
||||
"rgb(255, 0, 0)",
|
||||
"Body background is red"
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
add_task(async function test_pdf_with_link_in_response() {
|
||||
makePDFJSHandler();
|
||||
|
||||
await BrowserTestUtils.withNewTab(
|
||||
{ gBrowser, url: "about:blank" },
|
||||
async function (browser) {
|
||||
await waitForPdfJSCanvas(browser, `${TESTROOT}pdf_response_link.sjs?pdf`);
|
||||
const bodyBackgroundColor = await getBodyBackgroundColor(browser);
|
||||
Assert.notEqual(
|
||||
bodyBackgroundColor,
|
||||
"rgb(255, 0, 0)",
|
||||
"Body background is not red"
|
||||
);
|
||||
await waitForPdfJSClose(browser);
|
||||
}
|
||||
);
|
||||
});
|
||||
24
icecat/toolkit/components/pdfjs/test/pdf_response_link.sjs
Normal file
24
icecat/toolkit/components/pdfjs/test/pdf_response_link.sjs
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
const DATA = {
|
||||
pdf: {
|
||||
mimetype: "application/pdf",
|
||||
content:
|
||||
"%PDF-1.\ntrailer<</Root<</Pages<</Kids[<</MediaBox[0 0 3 3]>>]>>>>>>",
|
||||
},
|
||||
text: {
|
||||
mimetype: "text/plain",
|
||||
content: "hello world",
|
||||
},
|
||||
};
|
||||
|
||||
function handleRequest(request, response) {
|
||||
response.setHeader("Cache-Control", "no-cache", false);
|
||||
response.setHeader(
|
||||
"Link",
|
||||
"<data:text/css,body{background:red%20!important;}>; rel=stylesheet",
|
||||
false
|
||||
);
|
||||
response.setStatusLine(request.httpVersion, "200", "Found");
|
||||
const { mimetype, content } = DATA[request.queryString];
|
||||
response.setHeader("Content-Type", mimetype, false);
|
||||
response.write(content);
|
||||
}
|
||||
|
|
@ -127,7 +127,8 @@ nsresult ViaductRequest::LaunchRequest(
|
|||
nullptr, loadFlags);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel);
|
||||
nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel, &rv);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
nsCString method = ConvertMethod(request.method());
|
||||
rv = httpChannel->SetRequestMethod(method);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@
|
|||
use anyhow::{bail, Result};
|
||||
use crash_helper_common::{
|
||||
messages::{self},
|
||||
AncillaryData, BreakpadString, IPCClientChannel, IPCConnector, ProcessHandle,
|
||||
INVALID_ANCILLARY_DATA,
|
||||
AncillaryData, BreakpadString, IPCClientChannel, IPCConnector, IntoRawAncillaryData,
|
||||
ProcessHandle, RawAncillaryData, INVALID_ANCILLARY_DATA,
|
||||
};
|
||||
#[cfg(any(target_os = "android", target_os = "linux"))]
|
||||
use minidump_writer::minidump_writer::{AuxvType, DirectAuxvDumpInfo};
|
||||
|
|
@ -34,13 +34,12 @@ mod platform;
|
|||
pub struct CrashHelperClient {
|
||||
connector: IPCConnector,
|
||||
spawner_thread: Option<JoinHandle<Result<ProcessHandle>>>,
|
||||
helper_process: Option<ProcessHandle>,
|
||||
}
|
||||
|
||||
impl CrashHelperClient {
|
||||
fn set_crash_report_path(&mut self, path: OsString) -> Result<()> {
|
||||
let message = messages::SetCrashReportPath::new(path);
|
||||
self.connector.send_message(&message)?;
|
||||
self.connector.send_message(message)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
@ -57,52 +56,32 @@ impl CrashHelperClient {
|
|||
bail!("The crash helper process failed to launch");
|
||||
};
|
||||
|
||||
self.helper_process = Some(process_handle);
|
||||
self.connector.set_process(process_handle);
|
||||
}
|
||||
|
||||
if self.helper_process.is_none() {
|
||||
bail!("The crash helper process is not available");
|
||||
};
|
||||
let message = messages::RegisterChildProcess::new(server_endpoint.into_ancillary());
|
||||
self.connector.send_message(message)?;
|
||||
|
||||
// The endpoint will be sent to the crash helper process (and essentially dup'd on unix),
|
||||
// so we have to retain ownership of the server_endpoint using `as_ancillary()` until the
|
||||
// message is sent.
|
||||
let Ok(ancillary_data) = server_endpoint.as_ancillary(&self.helper_process) else {
|
||||
bail!("Could not convert the server IPC endpoint");
|
||||
};
|
||||
|
||||
let message = messages::RegisterChildProcess::new(ancillary_data);
|
||||
self.connector.send_message(&message)?;
|
||||
// We use `into_ancillary()` because the returned fd will stay in this process (so we don't
|
||||
// want to close it).
|
||||
let Ok(ancillary_data) = client_endpoint.into_ancillary(/* dst_process */ &None) else {
|
||||
bail!("Could not convert the local IPC endpoint");
|
||||
};
|
||||
|
||||
Ok(ancillary_data)
|
||||
Ok(client_endpoint.into_ancillary())
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "android", target_os = "linux"))]
|
||||
fn register_auxv_info(&mut self, pid: Pid, auxv_info: DirectAuxvDumpInfo) -> Result<()> {
|
||||
let message = messages::RegisterAuxvInfo::new(pid, auxv_info);
|
||||
self.connector.send_message(&message)?;
|
||||
self.connector.send_message(message)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "android", target_os = "linux"))]
|
||||
fn unregister_auxv_info(&mut self, pid: Pid) -> Result<()> {
|
||||
let message = messages::UnregisterAuxvInfo::new(pid);
|
||||
self.connector.send_message(&message)?;
|
||||
self.connector.send_message(message)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn transfer_crash_report(&mut self, pid: Pid) -> Result<CrashReport> {
|
||||
let message = messages::TransferMinidump::new(pid);
|
||||
self.connector.send_message(&message)?;
|
||||
|
||||
// HACK: Workaround for a macOS-specific bug
|
||||
#[cfg(target_os = "macos")]
|
||||
self.connector.poll(nix::poll::PollFlags::POLLIN)?;
|
||||
self.connector.send_message(message)?;
|
||||
|
||||
let reply = self
|
||||
.connector
|
||||
|
|
@ -232,10 +211,10 @@ pub unsafe extern "C" fn set_crash_report_path(
|
|||
#[no_mangle]
|
||||
pub unsafe extern "C" fn register_child_ipc_channel(
|
||||
client: *mut CrashHelperClient,
|
||||
) -> AncillaryData {
|
||||
) -> RawAncillaryData {
|
||||
let client = client.as_mut().unwrap();
|
||||
if let Ok(client_endpoint) = client.register_child_process() {
|
||||
client_endpoint
|
||||
client_endpoint.into_raw()
|
||||
} else {
|
||||
INVALID_ANCILLARY_DATA
|
||||
}
|
||||
|
|
@ -311,7 +290,7 @@ pub unsafe fn report_external_exception(
|
|||
let server_addr = crash_helper_common::server_addr(main_process_pid);
|
||||
if let Ok(connector) = IPCConnector::connect(&server_addr) {
|
||||
let _ = connector
|
||||
.send_message(&message)
|
||||
.send_message(message)
|
||||
.and_then(|_| connector.recv_reply::<messages::WindowsErrorReportingMinidumpReply>());
|
||||
}
|
||||
}
|
||||
|
|
@ -389,7 +368,7 @@ pub unsafe extern "C" fn unregister_child_auxv_info(
|
|||
// signal/exception-safe. We will access this endpoint only from within the
|
||||
// exception handler with bare syscalls so we can leave the `IPCConnector`
|
||||
// object behind.
|
||||
static CHILD_IPC_ENDPOINT: OnceLock<Box<AncillaryData>> = OnceLock::new();
|
||||
static CHILD_IPC_ENDPOINT: OnceLock<Box<RawAncillaryData>> = OnceLock::new();
|
||||
static RENDEZVOUS_FAILED: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
/// Let a client rendez-vous with the crash helper process. This step ensures
|
||||
|
|
@ -402,8 +381,8 @@ static RENDEZVOUS_FAILED: AtomicBool = AtomicBool::new(false);
|
|||
/// a valid pipe handle (on Windows) or a valid file descriptor (on all other
|
||||
/// platforms).
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn crash_helper_rendezvous(client_endpoint: AncillaryData) {
|
||||
let Ok(connector) = IPCConnector::from_ancillary(client_endpoint) else {
|
||||
pub unsafe extern "C" fn crash_helper_rendezvous(client_endpoint: RawAncillaryData) {
|
||||
let Ok(connector) = IPCConnector::from_raw_ancillary(client_endpoint) else {
|
||||
RENDEZVOUS_FAILED.store(true, Ordering::Relaxed);
|
||||
return;
|
||||
};
|
||||
|
|
@ -413,7 +392,7 @@ pub unsafe extern "C" fn crash_helper_rendezvous(client_endpoint: AncillaryData)
|
|||
CrashHelperClient::prepare_for_minidump(message.crash_helper_pid);
|
||||
assert!(
|
||||
CHILD_IPC_ENDPOINT
|
||||
.set(Box::new(connector.into_ancillary(&None).unwrap()))
|
||||
.set(Box::new(connector.into_raw_ancillary()))
|
||||
.is_ok(),
|
||||
"The crash_helper_rendezvous() function must only be called once"
|
||||
);
|
||||
|
|
|
|||
|
|
@ -3,21 +3,20 @@
|
|||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use anyhow::Result;
|
||||
use crash_helper_common::{IPCConnector, Pid};
|
||||
use std::os::fd::{FromRawFd, OwnedFd, RawFd};
|
||||
use crash_helper_common::{IPCConnector, Pid, RawAncillaryData};
|
||||
|
||||
use crate::CrashHelperClient;
|
||||
|
||||
impl CrashHelperClient {
|
||||
pub(crate) fn new(server_socket: RawFd) -> Result<CrashHelperClient> {
|
||||
pub(crate) fn new(server_socket: RawAncillaryData) -> Result<CrashHelperClient> {
|
||||
// SAFETY: The `server_socket` passed in from the application is valid
|
||||
let server_socket = unsafe { OwnedFd::from_raw_fd(server_socket) };
|
||||
let connector = IPCConnector::from_fd(server_socket)?;
|
||||
let connector = unsafe {
|
||||
IPCConnector::from_raw_ancillary(server_socket)?
|
||||
};
|
||||
|
||||
Ok(CrashHelperClient {
|
||||
connector,
|
||||
spawner_thread: None,
|
||||
helper_process: Some(()),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -34,7 +34,6 @@ impl CrashHelperClient {
|
|||
Ok(CrashHelperClient {
|
||||
connector: client_endpoint,
|
||||
spawner_thread: None,
|
||||
helper_process: Some(()),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -52,7 +52,6 @@ impl CrashHelperClient {
|
|||
Ok(CrashHelperClient {
|
||||
connector: client_endpoint,
|
||||
spawner_thread: Some(spawner_thread),
|
||||
helper_process: None,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ nix = { version = "0.30", features = ["fs", "poll", "socket", "uio"] }
|
|||
minidump-writer = "0.10"
|
||||
|
||||
[target."cfg(target_os = \"windows\")".dependencies]
|
||||
getrandom = { version = "0.3" }
|
||||
windows-sys = { version = "0.52", features = [
|
||||
"Win32_Foundation",
|
||||
"Win32_Security",
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ use std::{
|
|||
os::unix::ffi::OsStringExt,
|
||||
};
|
||||
|
||||
use crate::{errors::MessageError, BreakpadString};
|
||||
use crate::{messages::MessageError, BreakpadString};
|
||||
|
||||
use super::BreakpadChar;
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ use std::{
|
|||
os::windows::ffi::{OsStrExt, OsStringExt},
|
||||
};
|
||||
|
||||
use crate::{errors::MessageError, BreakpadChar, BreakpadString};
|
||||
use crate::{messages::MessageError, BreakpadChar, BreakpadString};
|
||||
|
||||
// BreakpadString trait implementation for Windows native UTF-16 strings
|
||||
impl BreakpadString for OsString {
|
||||
|
|
|
|||
|
|
@ -2,60 +2,37 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use std::{
|
||||
array::TryFromSliceError,
|
||||
ffi::{FromBytesWithNulError, NulError},
|
||||
num::TryFromIntError,
|
||||
};
|
||||
use std::num::TryFromIntError;
|
||||
use thiserror::Error;
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
use nix::errno::Errno as SystemError;
|
||||
pub use nix::errno::Errno as SystemError;
|
||||
#[cfg(target_os = "windows")]
|
||||
use windows_sys::Win32::Foundation::WIN32_ERROR as SystemError;
|
||||
pub use windows_sys::Win32::Foundation::WIN32_ERROR as SystemError;
|
||||
|
||||
use crate::{
|
||||
messages::{self, MessageError},
|
||||
platform::PlatformError,
|
||||
};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum IPCError {
|
||||
#[error("Message error")]
|
||||
BadMessage(#[from] MessageError),
|
||||
#[error("Generic system error: {0}")]
|
||||
System(SystemError),
|
||||
#[error("Could not bind socket to an address, error: {0}")]
|
||||
BindFailed(SystemError),
|
||||
#[error("Could not listen on a socket, error: {0}")]
|
||||
ListenFailed(SystemError),
|
||||
#[error("Could not accept an incoming connection, error: {0}")]
|
||||
AcceptFailed(SystemError),
|
||||
#[error("Could not connect to a socket, error: {0}")]
|
||||
#[error("Could not connect to a socket: {0}")]
|
||||
ConnectionFailure(SystemError),
|
||||
#[error("Could not send data, error: {0}")]
|
||||
TransmissionFailure(SystemError),
|
||||
#[error("Could not receive data, error: {0}")]
|
||||
ReceptionFailure(SystemError),
|
||||
#[error("Error while waiting for events, error: {0:?}")]
|
||||
WaitingFailure(Option<SystemError>),
|
||||
#[error("Failed to create a connector: {0}")]
|
||||
CreationFailure(PlatformError),
|
||||
#[error("Buffer length exceeds a 32-bit integer")]
|
||||
InvalidSize(#[from] TryFromIntError),
|
||||
#[error("Error while parsing a file descriptor string")]
|
||||
ParseError,
|
||||
#[error("Failed to duplicate clone handle")]
|
||||
CloneHandleFailed(#[source] std::io::Error),
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum MessageError {
|
||||
#[error("Truncated message")]
|
||||
Truncated,
|
||||
#[error("Message kind is invalid")]
|
||||
InvalidKind,
|
||||
#[error("The message contained an invalid payload")]
|
||||
InvalidData,
|
||||
#[error("Missing ancillary data")]
|
||||
MissingAncillary,
|
||||
#[error("Invalid message size")]
|
||||
InvalidSize(#[from] TryFromSliceError),
|
||||
#[error("Missing nul terminator")]
|
||||
MissingNul(#[from] FromBytesWithNulError),
|
||||
#[error("Missing nul terminator")]
|
||||
InteriorNul(#[from] NulError),
|
||||
#[error("Could not receive data: {0}")]
|
||||
ReceptionFailure(PlatformError),
|
||||
#[error("An operation timed out")]
|
||||
Timeout,
|
||||
#[error("Could not send data: {0}")]
|
||||
TransmissionFailure(PlatformError),
|
||||
#[error("Unexpected message of kind: {0:?}")]
|
||||
UnexpectedMessage(messages::Kind),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,24 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::{errors::IPCError, platform::PlatformError, IPCListenerError};
|
||||
|
||||
/*****************************************************************************
|
||||
* Error definitions *
|
||||
*****************************************************************************/
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum IPCChannelError {
|
||||
#[error("Could not create connector: {0}")]
|
||||
Connector(#[from] IPCError),
|
||||
#[error("Could not create a listener: {0}")]
|
||||
Listener(#[from] IPCListenerError),
|
||||
#[error("Could not create a socketpair: {0}")]
|
||||
SocketPair(#[from] PlatformError),
|
||||
}
|
||||
|
||||
/*****************************************************************************
|
||||
* Windows *
|
||||
*****************************************************************************/
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ use std::process;
|
|||
use crate::platform::linux::unix_socketpair;
|
||||
#[cfg(target_os = "macos")]
|
||||
use crate::platform::macos::unix_socketpair;
|
||||
use crate::{errors::IPCError, IPCConnector, IPCListener, Pid};
|
||||
use crate::{ipc_channel::IPCChannelError, IPCConnector, IPCListener, Pid};
|
||||
|
||||
pub struct IPCChannel {
|
||||
listener: IPCListener,
|
||||
|
|
@ -21,11 +21,11 @@ impl IPCChannel {
|
|||
/// will use the current process PID as part of its address and two
|
||||
/// connected endpoints. The listener and the server-side endpoint can be
|
||||
/// inherited by a child process, the client-side endpoint cannot.
|
||||
pub fn new() -> Result<IPCChannel, IPCError> {
|
||||
pub fn new() -> Result<IPCChannel, IPCChannelError> {
|
||||
let listener = IPCListener::new(process::id() as Pid)?;
|
||||
|
||||
// Only the server-side socket will be left open after an exec().
|
||||
let pair = unix_socketpair().map_err(IPCError::System)?;
|
||||
let pair = unix_socketpair().map_err(IPCChannelError::SocketPair)?;
|
||||
let client_endpoint = IPCConnector::from_fd(pair.0)?;
|
||||
let server_endpoint = IPCConnector::from_fd_inheritable(pair.1)?;
|
||||
|
||||
|
|
@ -52,8 +52,8 @@ pub struct IPCClientChannel {
|
|||
impl IPCClientChannel {
|
||||
/// Create a new IPC channel for use between one of the browser's child
|
||||
/// processes and the crash helper.
|
||||
pub fn new() -> Result<IPCClientChannel, IPCError> {
|
||||
let pair = unix_socketpair().map_err(IPCError::System)?;
|
||||
pub fn new() -> Result<IPCClientChannel, IPCChannelError> {
|
||||
let pair = unix_socketpair().map_err(IPCChannelError::SocketPair)?;
|
||||
let client_endpoint = IPCConnector::from_fd(pair.0)?;
|
||||
let server_endpoint = IPCConnector::from_fd(pair.1)?;
|
||||
|
||||
|
|
|
|||
|
|
@ -2,13 +2,14 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use std::{ffi::CString, hash::RandomState, process};
|
||||
use std::{ffi::CString, process};
|
||||
|
||||
use windows_sys::Win32::Foundation::ERROR_ACCESS_DENIED;
|
||||
use windows_sys::Win32::Foundation::{ERROR_ACCESS_DENIED, ERROR_ADDRESS_ALREADY_ASSOCIATED};
|
||||
|
||||
use crate::{
|
||||
errors::IPCError,
|
||||
platform::windows::{get_last_error, server_addr},
|
||||
ipc_channel::IPCChannelError,
|
||||
ipc_listener::IPCListenerError,
|
||||
platform::windows::{server_addr, PlatformError},
|
||||
IPCConnector, IPCListener, Pid,
|
||||
};
|
||||
|
||||
|
|
@ -22,7 +23,7 @@ impl IPCChannel {
|
|||
/// Create a new IPCChannel, this includes a listening endpoint that
|
||||
/// will use the current process PID as part of its address and two
|
||||
/// connected endpoints.
|
||||
pub fn new() -> Result<IPCChannel, IPCError> {
|
||||
pub fn new() -> Result<IPCChannel, IPCChannelError> {
|
||||
let pid = process::id() as Pid;
|
||||
let mut listener = IPCListener::new(server_addr(pid))?;
|
||||
listener.listen()?;
|
||||
|
|
@ -52,7 +53,7 @@ pub struct IPCClientChannel {
|
|||
impl IPCClientChannel {
|
||||
/// Create a new IPC channel for use between one of the browser's child
|
||||
/// processes and the crash helper.
|
||||
pub fn new() -> Result<IPCClientChannel, IPCError> {
|
||||
pub fn new() -> Result<IPCClientChannel, IPCChannelError> {
|
||||
let mut listener = Self::create_listener()?;
|
||||
listener.listen()?;
|
||||
let client_endpoint = IPCConnector::connect(listener.address())?;
|
||||
|
|
@ -64,14 +65,15 @@ impl IPCClientChannel {
|
|||
})
|
||||
}
|
||||
|
||||
fn create_listener() -> Result<IPCListener, IPCError> {
|
||||
fn create_listener() -> Result<IPCListener, IPCListenerError> {
|
||||
const ATTEMPTS: u32 = 5;
|
||||
|
||||
// We pick the listener name at random, as unlikely as it may be there
|
||||
// could be clashes so try a few times before giving up.
|
||||
for _i in 0..ATTEMPTS {
|
||||
use std::hash::{BuildHasher, Hasher};
|
||||
let random_id = RandomState::new().build_hasher().finish();
|
||||
let Ok(random_id) = getrandom::u64() else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let pipe_name = CString::new(format!(
|
||||
"\\\\.\\pipe\\gecko-crash-helper-child-pipe.{random_id:}"
|
||||
|
|
@ -79,14 +81,19 @@ impl IPCClientChannel {
|
|||
.unwrap();
|
||||
match IPCListener::new(pipe_name) {
|
||||
Ok(listener) => return Ok(listener),
|
||||
Err(_error @ IPCError::System(ERROR_ACCESS_DENIED)) => {} // Try again
|
||||
Err(
|
||||
_error @ IPCListenerError::CreationError(PlatformError::CreatePipeFailure(
|
||||
ERROR_ACCESS_DENIED,
|
||||
)),
|
||||
) => {} // Try again
|
||||
Err(error) => return Err(error),
|
||||
}
|
||||
}
|
||||
|
||||
// If we got to this point return whatever was the last error we
|
||||
// encountered along the way.
|
||||
Err(IPCError::System(get_last_error()))
|
||||
// If we got to this point give up.
|
||||
Err(IPCListenerError::CreationError(
|
||||
PlatformError::CreatePipeFailure(ERROR_ADDRESS_ALREADY_ASSOCIATED),
|
||||
))
|
||||
}
|
||||
|
||||
/// Deconstruct the IPC channel, returning the listening endpoint,
|
||||
|
|
|
|||
|
|
@ -2,12 +2,14 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::messages::Header;
|
||||
|
||||
pub enum IPCEvent {
|
||||
Connect(IPCConnector),
|
||||
Header(usize, Header),
|
||||
Disconnect(usize),
|
||||
Connect(Rc<IPCConnector>),
|
||||
Message(IPCConnectorKey, Header, Vec<u8>, Option<AncillaryData>),
|
||||
Disconnect(IPCConnectorKey),
|
||||
}
|
||||
|
||||
/*****************************************************************************
|
||||
|
|
@ -15,7 +17,9 @@ pub enum IPCEvent {
|
|||
*****************************************************************************/
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
pub use windows::{AncillaryData, IPCConnector, INVALID_ANCILLARY_DATA};
|
||||
pub use windows::{
|
||||
AncillaryData, IPCConnector, IPCConnectorKey, RawAncillaryData, INVALID_ANCILLARY_DATA,
|
||||
};
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
pub(crate) mod windows;
|
||||
|
|
@ -25,7 +29,9 @@ pub(crate) mod windows;
|
|||
*****************************************************************************/
|
||||
|
||||
#[cfg(any(target_os = "android", target_os = "linux", target_os = "macos"))]
|
||||
pub use unix::{AncillaryData, IPCConnector, INVALID_ANCILLARY_DATA};
|
||||
pub use unix::{
|
||||
AncillaryData, IPCConnector, IPCConnectorKey, RawAncillaryData, INVALID_ANCILLARY_DATA,
|
||||
};
|
||||
|
||||
#[cfg(any(target_os = "android", target_os = "linux", target_os = "macos"))]
|
||||
pub(crate) mod unix;
|
||||
|
|
|
|||
|
|
@ -3,14 +3,12 @@
|
|||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#[cfg(any(target_os = "android", target_os = "linux"))]
|
||||
use crate::platform::linux::{
|
||||
set_socket_cloexec, set_socket_default_flags,
|
||||
};
|
||||
use crate::platform::linux::{set_socket_cloexec, set_socket_default_flags};
|
||||
#[cfg(target_os = "macos")]
|
||||
use crate::platform::macos::{
|
||||
set_socket_cloexec, set_socket_default_flags,
|
||||
use crate::platform::macos::{set_socket_cloexec, set_socket_default_flags};
|
||||
use crate::{
|
||||
ignore_eintr, platform::PlatformError, IntoRawAncillaryData, ProcessHandle, IO_TIMEOUT,
|
||||
};
|
||||
use crate::{ignore_eintr, ProcessHandle, IO_TIMEOUT};
|
||||
|
||||
use nix::{
|
||||
cmsg_space,
|
||||
|
|
@ -21,7 +19,7 @@ use nix::{
|
|||
use std::{
|
||||
ffi::{CStr, CString},
|
||||
io::{IoSlice, IoSliceMut},
|
||||
os::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd},
|
||||
os::fd::{AsFd, AsRawFd, FromRawFd, IntoRawFd, OwnedFd, RawFd},
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
|
|
@ -30,10 +28,19 @@ use crate::{
|
|||
messages::{self, Message},
|
||||
};
|
||||
|
||||
pub type AncillaryData = RawFd;
|
||||
pub type RawAncillaryData = RawFd;
|
||||
pub type AncillaryData = OwnedFd;
|
||||
|
||||
impl IntoRawAncillaryData for AncillaryData {
|
||||
fn into_raw(self) -> RawAncillaryData {
|
||||
self.into_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
// This must match `kInvalidHandle` in `mfbt/UniquePtrExt.h`
|
||||
pub const INVALID_ANCILLARY_DATA: AncillaryData = -1;
|
||||
pub const INVALID_ANCILLARY_DATA: RawAncillaryData = -1;
|
||||
|
||||
pub type IPCConnectorKey = RawFd;
|
||||
|
||||
pub struct IPCConnector {
|
||||
socket: OwnedFd,
|
||||
|
|
@ -43,29 +50,43 @@ impl IPCConnector {
|
|||
/// Create a new connector from an already connected socket. The
|
||||
/// `FD_CLOEXEC` flag will be set on the underlying socket and thus it
|
||||
/// will not be possible to inerhit this connector in a child process.
|
||||
pub fn from_fd(socket: OwnedFd) -> Result<IPCConnector, IPCError> {
|
||||
pub(crate) fn from_fd(socket: OwnedFd) -> Result<IPCConnector, IPCError> {
|
||||
let connector = IPCConnector::from_fd_inheritable(socket)?;
|
||||
set_socket_cloexec(connector.socket.as_fd()).map_err(IPCError::System)?;
|
||||
set_socket_cloexec(connector.socket.as_fd()).map_err(IPCError::CreationFailure)?;
|
||||
Ok(connector)
|
||||
}
|
||||
|
||||
/// Create a new connector from an already connected socket. The
|
||||
/// `FD_CLOEXEC` flag will not be set on the underlying socket and thus it
|
||||
/// will be possible to inherit this connector in a child process.
|
||||
pub fn from_fd_inheritable(socket: OwnedFd) -> Result<IPCConnector, IPCError> {
|
||||
set_socket_default_flags(socket.as_fd()).map_err(IPCError::System)?;
|
||||
pub(crate) fn from_fd_inheritable(socket: OwnedFd) -> Result<IPCConnector, IPCError> {
|
||||
set_socket_default_flags(socket.as_fd()).map_err(IPCError::CreationFailure)?;
|
||||
Ok(IPCConnector { socket })
|
||||
}
|
||||
|
||||
pub fn from_ancillary(ancillary_data: AncillaryData) -> Result<IPCConnector, IPCError> {
|
||||
IPCConnector::from_fd(unsafe { OwnedFd::from_raw_fd(ancillary_data) })
|
||||
pub fn from_ancillary(socket: AncillaryData) -> Result<IPCConnector, IPCError> {
|
||||
IPCConnector::from_fd(socket)
|
||||
}
|
||||
|
||||
/// Create a connector from a raw file descriptor.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The `ancillary_data` argument must be an open file descriptor
|
||||
/// representing a connected Unix socket.
|
||||
pub unsafe fn from_raw_ancillary(
|
||||
ancillary_data: RawAncillaryData,
|
||||
) -> Result<IPCConnector, IPCError> {
|
||||
IPCConnector::from_fd(OwnedFd::from_raw_fd(ancillary_data))
|
||||
}
|
||||
|
||||
pub fn set_process(&mut self, _process: ProcessHandle) {}
|
||||
|
||||
/// Serialize this connector into a string that can be passed on the
|
||||
/// command-line to a child process. This only works for newly
|
||||
/// created connectors because they are explicitly created as inheritable.
|
||||
pub fn serialize(&self) -> CString {
|
||||
CString::new(self.socket.as_raw_fd().to_string()).unwrap()
|
||||
CString::new(self.as_raw().to_string()).unwrap()
|
||||
}
|
||||
|
||||
/// Deserialize a connector from an argument passed on the command-line.
|
||||
|
|
@ -77,47 +98,43 @@ impl IPCConnector {
|
|||
Ok(IPCConnector { socket })
|
||||
}
|
||||
|
||||
fn raw_fd(&self) -> RawFd {
|
||||
pub fn into_ancillary(self) -> AncillaryData {
|
||||
self.socket
|
||||
}
|
||||
|
||||
pub fn into_raw_ancillary(self) -> RawAncillaryData {
|
||||
self.socket.into_raw()
|
||||
}
|
||||
|
||||
pub(crate) fn as_raw(&self) -> RawFd {
|
||||
self.socket.as_raw_fd()
|
||||
}
|
||||
|
||||
pub fn into_ancillary(
|
||||
self,
|
||||
_dst_process: &Option<ProcessHandle>,
|
||||
) -> Result<AncillaryData, IPCError> {
|
||||
Ok(self.socket.into_raw_fd())
|
||||
pub fn key(&self) -> IPCConnectorKey {
|
||||
self.socket.as_raw_fd()
|
||||
}
|
||||
|
||||
/// Like into_ancillary, but the IPCConnector retains ownership of the file descriptor (so be
|
||||
/// sure to use the result during the lifetime of the IPCConnector).
|
||||
pub fn as_ancillary(
|
||||
&self,
|
||||
_dst_process: &Option<ProcessHandle>,
|
||||
) -> Result<AncillaryData, IPCError> {
|
||||
Ok(self.raw_fd())
|
||||
}
|
||||
|
||||
pub fn as_raw_ref(&self) -> BorrowedFd<'_> {
|
||||
self.socket.as_fd()
|
||||
}
|
||||
|
||||
pub fn poll(&self, flags: PollFlags) -> Result<(), Errno> {
|
||||
fn poll(&self, flags: PollFlags) -> Result<(), PlatformError> {
|
||||
let timeout = PollTimeout::from(IO_TIMEOUT);
|
||||
let res = ignore_eintr!(poll(
|
||||
&mut [PollFd::new(self.socket.as_fd(), flags)],
|
||||
timeout
|
||||
));
|
||||
match res {
|
||||
Err(e) => Err(e),
|
||||
Ok(_res @ 0) => Err(Errno::EAGAIN),
|
||||
Err(e) => Err(PlatformError::PollFailure(e)),
|
||||
Ok(_res @ 0) => Err(PlatformError::PollFailure(Errno::EAGAIN)),
|
||||
Ok(_) => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_message(&self, message: &dyn Message) -> Result<(), IPCError> {
|
||||
pub fn send_message<T>(&self, message: T) -> Result<(), IPCError>
|
||||
where
|
||||
T: Message,
|
||||
{
|
||||
self.send(&message.header(), None)
|
||||
.map_err(IPCError::TransmissionFailure)?;
|
||||
self.send(&message.payload(), message.ancillary_payload())
|
||||
let (payload, ancillary_data) = message.into_payload();
|
||||
self.send(&payload, ancillary_data)
|
||||
.map_err(IPCError::TransmissionFailure)
|
||||
}
|
||||
|
||||
|
|
@ -125,23 +142,28 @@ impl IPCConnector {
|
|||
where
|
||||
T: Message,
|
||||
{
|
||||
// HACK: Workaround for a macOS-specific bug
|
||||
#[cfg(target_os = "macos")]
|
||||
self.poll(PollFlags::POLLIN)
|
||||
.map_err(IPCError::ReceptionFailure)?;
|
||||
|
||||
let header = self.recv_header()?;
|
||||
|
||||
if header.kind != T::kind() {
|
||||
return Err(IPCError::ReceptionFailure(Errno::EBADMSG));
|
||||
return Err(IPCError::UnexpectedMessage(header.kind));
|
||||
}
|
||||
|
||||
let (data, _) = self.recv(header.size).map_err(IPCError::ReceptionFailure)?;
|
||||
let (data, _) = self.recv(header.size)?;
|
||||
T::decode(&data, None).map_err(IPCError::from)
|
||||
}
|
||||
|
||||
fn send_nonblock(&self, buff: &[u8], fd: Option<AncillaryData>) -> Result<(), Errno> {
|
||||
fn send_nonblock(&self, buff: &[u8], fd: &Option<AncillaryData>) -> Result<(), PlatformError> {
|
||||
let iov = [IoSlice::new(buff)];
|
||||
let scm_fds: Vec<i32> = fd.map_or(vec![], |fd| vec![fd]);
|
||||
let scm_fds: Vec<i32> = fd.iter().map(|fd| fd.as_raw_fd()).collect();
|
||||
let scm = ControlMessage::ScmRights(&scm_fds);
|
||||
|
||||
let res = ignore_eintr!(sendmsg::<()>(
|
||||
self.raw_fd(),
|
||||
self.as_raw(),
|
||||
&iov,
|
||||
&[scm],
|
||||
MsgFlags::empty(),
|
||||
|
|
@ -153,45 +175,44 @@ impl IPCConnector {
|
|||
if bytes_sent == buff.len() {
|
||||
Ok(())
|
||||
} else {
|
||||
// TODO: This should never happen but we might want to put a
|
||||
// better error message here.
|
||||
Err(Errno::EMSGSIZE)
|
||||
Err(PlatformError::SendTooShort {
|
||||
expected: buff.len(),
|
||||
sent: bytes_sent,
|
||||
})
|
||||
}
|
||||
}
|
||||
Err(code) => Err(code),
|
||||
Err(code) => Err(PlatformError::SendFailure(code)),
|
||||
}
|
||||
}
|
||||
|
||||
fn send(&self, buff: &[u8], fd: Option<AncillaryData>) -> Result<(), Errno> {
|
||||
let res = self.send_nonblock(buff, fd);
|
||||
fn send(&self, buff: &[u8], fd: Option<AncillaryData>) -> Result<(), PlatformError> {
|
||||
let res = self.send_nonblock(buff, &fd);
|
||||
match res {
|
||||
Err(_code @ Errno::EAGAIN) => {
|
||||
Err(PlatformError::SendFailure(Errno::EAGAIN)) => {
|
||||
// If the socket was not ready to send data wait for it to
|
||||
// become unblocked then retry sending just once.
|
||||
self.poll(PollFlags::POLLOUT)?;
|
||||
self.send_nonblock(buff, fd)
|
||||
self.send_nonblock(buff, &fd)
|
||||
}
|
||||
_ => res,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn recv_header(&self) -> Result<messages::Header, IPCError> {
|
||||
let (header, _) = self
|
||||
.recv(messages::HEADER_SIZE)
|
||||
.map_err(IPCError::ReceptionFailure)?;
|
||||
pub(crate) fn recv_header(&self) -> Result<messages::Header, IPCError> {
|
||||
let (header, _) = self.recv(messages::HEADER_SIZE)?;
|
||||
messages::Header::decode(&header).map_err(IPCError::BadMessage)
|
||||
}
|
||||
|
||||
fn recv_nonblock(
|
||||
&self,
|
||||
expected_size: usize,
|
||||
) -> Result<(Vec<u8>, Option<AncillaryData>), Errno> {
|
||||
) -> Result<(Vec<u8>, Option<AncillaryData>), PlatformError> {
|
||||
let mut buff: Vec<u8> = vec![0; expected_size];
|
||||
let mut cmsg_buffer = cmsg_space!(RawFd);
|
||||
let mut iov = [IoSliceMut::new(&mut buff)];
|
||||
|
||||
let res = ignore_eintr!(recvmsg::<()>(
|
||||
self.raw_fd(),
|
||||
self.as_raw(),
|
||||
&mut iov,
|
||||
Some(&mut cmsg_buffer),
|
||||
MsgFlags::empty(),
|
||||
|
|
@ -207,44 +228,47 @@ impl IPCConnector {
|
|||
let res = match res {
|
||||
#[cfg(target_os = "macos")]
|
||||
Err(_code @ Errno::ENOMEM) => ignore_eintr!(recvmsg::<()>(
|
||||
self.raw_fd(),
|
||||
self.as_raw(),
|
||||
&mut iov,
|
||||
Some(&mut cmsg_buffer),
|
||||
MsgFlags::empty(),
|
||||
))?,
|
||||
Err(e) => return Err(e),
|
||||
Err(e) => return Err(PlatformError::ReceiveFailure(e)),
|
||||
Ok(val) => val,
|
||||
};
|
||||
|
||||
let fd = if let Some(cmsg) = res.cmsgs()?.next() {
|
||||
if let ControlMessageOwned::ScmRights(fds) = cmsg {
|
||||
fds.first().copied()
|
||||
fds.first().map(|&fd| unsafe { OwnedFd::from_raw_fd(fd) })
|
||||
} else {
|
||||
return Err(Errno::EBADMSG);
|
||||
return Err(PlatformError::ReceiveMissingCredentials);
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if res.bytes != expected_size {
|
||||
// TODO: This should only ever happen if the other side has gone rogue,
|
||||
// we need a better error message here.
|
||||
return Err(Errno::EBADMSG);
|
||||
return Err(PlatformError::ReceiveTooShort {
|
||||
expected: expected_size,
|
||||
received: res.bytes,
|
||||
});
|
||||
}
|
||||
|
||||
Ok((buff, fd))
|
||||
}
|
||||
|
||||
pub fn recv(&self, expected_size: usize) -> Result<(Vec<u8>, Option<AncillaryData>), Errno> {
|
||||
pub fn recv(&self, expected_size: usize) -> Result<(Vec<u8>, Option<AncillaryData>), IPCError> {
|
||||
let res = self.recv_nonblock(expected_size);
|
||||
match res {
|
||||
Err(_code @ Errno::EAGAIN) => {
|
||||
Err(PlatformError::ReceiveFailure(Errno::EAGAIN)) => {
|
||||
// If the socket was not ready to receive data wait for it to
|
||||
// become unblocked then retry receiving just once.
|
||||
self.poll(PollFlags::POLLIN)?;
|
||||
self.poll(PollFlags::POLLIN)
|
||||
.map_err(IPCError::ReceptionFailure)?;
|
||||
self.recv_nonblock(expected_size)
|
||||
}
|
||||
_ => res,
|
||||
}
|
||||
.map_err(IPCError::ReceptionFailure)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,23 +4,28 @@
|
|||
|
||||
use crate::{
|
||||
errors::IPCError,
|
||||
messages::{self, Message},
|
||||
platform::windows::{create_manual_reset_event, get_last_error, OverlappedOperation},
|
||||
ProcessHandle, IO_TIMEOUT,
|
||||
messages::{self, Message, HEADER_SIZE},
|
||||
platform::windows::{
|
||||
create_manual_reset_event, get_last_error, OverlappedOperation, PlatformError,
|
||||
},
|
||||
IntoRawAncillaryData, IO_TIMEOUT,
|
||||
};
|
||||
|
||||
use std::{
|
||||
ffi::{c_void, CStr, OsString},
|
||||
os::windows::io::{AsRawHandle, FromRawHandle, IntoRawHandle, OwnedHandle, RawHandle},
|
||||
ffi::{CStr, OsString},
|
||||
io::Error,
|
||||
os::windows::io::{
|
||||
AsHandle, AsRawHandle, FromRawHandle, IntoRawHandle, OwnedHandle, RawHandle,
|
||||
},
|
||||
ptr::null_mut,
|
||||
rc::Rc,
|
||||
str::FromStr,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use windows_sys::Win32::{
|
||||
Foundation::{
|
||||
DuplicateHandle, GetLastError, DUPLICATE_CLOSE_SOURCE, DUPLICATE_SAME_ACCESS,
|
||||
ERROR_FILE_NOT_FOUND, ERROR_INVALID_MESSAGE, ERROR_PIPE_BUSY, FALSE, HANDLE,
|
||||
INVALID_HANDLE_VALUE, WAIT_TIMEOUT,
|
||||
DuplicateHandle, DUPLICATE_CLOSE_SOURCE, DUPLICATE_SAME_ACCESS, ERROR_FILE_NOT_FOUND,
|
||||
ERROR_PIPE_BUSY, FALSE, HANDLE, INVALID_HANDLE_VALUE,
|
||||
},
|
||||
Security::SECURITY_ATTRIBUTES,
|
||||
Storage::FileSystem::{
|
||||
|
|
@ -33,17 +38,24 @@ use windows_sys::Win32::{
|
|||
},
|
||||
};
|
||||
|
||||
pub type AncillaryData = HANDLE;
|
||||
pub type AncillaryData = OwnedHandle;
|
||||
pub type RawAncillaryData = HANDLE;
|
||||
|
||||
impl IntoRawAncillaryData for AncillaryData {
|
||||
fn into_raw(self) -> RawAncillaryData {
|
||||
self.into_raw_handle() as HANDLE
|
||||
}
|
||||
}
|
||||
|
||||
// This must match `kInvalidHandle` in `mfbt/UniquePtrExt.h`
|
||||
pub const INVALID_ANCILLARY_DATA: AncillaryData = 0;
|
||||
pub const INVALID_ANCILLARY_DATA: RawAncillaryData = 0;
|
||||
|
||||
const HANDLE_SIZE: usize = size_of::<HANDLE>();
|
||||
|
||||
// We encode handles at the beginning of every transmitted message. This
|
||||
// function extracts the handle (if present) and returns it together with
|
||||
// the rest of the buffer.
|
||||
fn extract_buffer_and_handle(buffer: Vec<u8>) -> Result<(Vec<u8>, Option<HANDLE>), IPCError> {
|
||||
fn extract_buffer_and_handle(buffer: Vec<u8>) -> Result<(Vec<u8>, Option<OwnedHandle>), IPCError> {
|
||||
let handle_bytes = &buffer[0..HANDLE_SIZE];
|
||||
let data = &buffer[HANDLE_SIZE..];
|
||||
let handle_bytes: Result<[u8; HANDLE_SIZE], _> = handle_bytes.try_into();
|
||||
|
|
@ -52,39 +64,57 @@ fn extract_buffer_and_handle(buffer: Vec<u8>) -> Result<(Vec<u8>, Option<HANDLE>
|
|||
};
|
||||
let handle = match HANDLE::from_ne_bytes(handle_bytes) {
|
||||
INVALID_ANCILLARY_DATA => None,
|
||||
handle => Some(handle),
|
||||
handle => Some(unsafe { OwnedHandle::from_raw_handle(handle as RawHandle) }),
|
||||
};
|
||||
|
||||
Ok((data.to_vec(), handle))
|
||||
}
|
||||
|
||||
pub type IPCConnectorKey = usize;
|
||||
|
||||
pub struct IPCConnector {
|
||||
handle: OwnedHandle,
|
||||
/// A connected pipe handle
|
||||
handle: Rc<OwnedHandle>,
|
||||
/// A handle to an event which will be used for overlapped I/O on the pipe
|
||||
event: OwnedHandle,
|
||||
overlapped: Option<OverlappedOperation>,
|
||||
/// The process at the other end of the pipe, this is needed to send
|
||||
/// ancillary data and a send operation will fail if not set.
|
||||
process: Option<OwnedHandle>,
|
||||
}
|
||||
|
||||
impl IPCConnector {
|
||||
pub fn new(handle: OwnedHandle) -> Result<IPCConnector, IPCError> {
|
||||
let event = create_manual_reset_event()?;
|
||||
pub fn from_ancillary(handle: OwnedHandle) -> Result<IPCConnector, IPCError> {
|
||||
let event = create_manual_reset_event().map_err(IPCError::CreationFailure)?;
|
||||
|
||||
Ok(IPCConnector {
|
||||
handle,
|
||||
handle: Rc::new(handle),
|
||||
event,
|
||||
overlapped: None,
|
||||
process: None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn from_ancillary(ancillary_data: AncillaryData) -> Result<IPCConnector, IPCError> {
|
||||
IPCConnector::new(unsafe { OwnedHandle::from_raw_handle(ancillary_data as RawHandle) })
|
||||
/// Create a connector from a raw handle.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The `ancillary_data` argument must be a valid HANDLE representing the
|
||||
/// endpoint of a named pipe.
|
||||
pub unsafe fn from_raw_ancillary(
|
||||
ancillary_data: RawAncillaryData,
|
||||
) -> Result<IPCConnector, IPCError> {
|
||||
IPCConnector::from_ancillary(OwnedHandle::from_raw_handle(ancillary_data as RawHandle))
|
||||
}
|
||||
|
||||
pub fn as_raw(&self) -> HANDLE {
|
||||
pub fn set_process(&mut self, process: OwnedHandle) {
|
||||
self.process = Some(process);
|
||||
}
|
||||
|
||||
pub(crate) fn as_raw(&self) -> HANDLE {
|
||||
self.handle.as_raw_handle() as HANDLE
|
||||
}
|
||||
|
||||
pub fn event_raw_handle(&self) -> HANDLE {
|
||||
self.event.as_raw_handle() as HANDLE
|
||||
pub fn key(&self) -> IPCConnectorKey {
|
||||
self.handle.as_raw_handle() as IPCConnectorKey
|
||||
}
|
||||
|
||||
pub fn connect(server_addr: &CStr) -> Result<IPCConnector, IPCError> {
|
||||
|
|
@ -120,10 +150,10 @@ impl IPCConnector {
|
|||
let elapsed = now.elapsed();
|
||||
|
||||
if elapsed >= timeout {
|
||||
return Err(IPCError::System(WAIT_TIMEOUT)); // TODO: We need a dedicated error
|
||||
return Err(IPCError::Timeout);
|
||||
}
|
||||
|
||||
let error = unsafe { GetLastError() };
|
||||
let error = get_last_error();
|
||||
|
||||
// The pipe might have not been created yet or it might be busy.
|
||||
if (error == ERROR_FILE_NOT_FOUND) || (error == ERROR_PIPE_BUSY) {
|
||||
|
|
@ -134,14 +164,14 @@ impl IPCConnector {
|
|||
(timeout - elapsed).as_millis() as u32,
|
||||
)
|
||||
};
|
||||
let error = unsafe { GetLastError() };
|
||||
let error = get_last_error();
|
||||
|
||||
// If the pipe hasn't been created yet loop over and try again
|
||||
if (res == FALSE) && (error != ERROR_FILE_NOT_FOUND) {
|
||||
return Err(IPCError::System(error));
|
||||
return Err(IPCError::ConnectionFailure(error));
|
||||
}
|
||||
} else {
|
||||
return Err(IPCError::System(error));
|
||||
return Err(IPCError::ConnectionFailure(error));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -158,12 +188,11 @@ impl IPCConnector {
|
|||
)
|
||||
};
|
||||
if res == FALSE {
|
||||
return Err(IPCError::System(unsafe { GetLastError() }));
|
||||
return Err(IPCError::ConnectionFailure(get_last_error()));
|
||||
}
|
||||
|
||||
// SAFETY: The raw pipe handle is guaranteed to be open at this point
|
||||
let handle = unsafe { OwnedHandle::from_raw_handle(pipe as RawHandle) };
|
||||
IPCConnector::new(handle)
|
||||
// SAFETY: We've verified above that the pipe handle is valid
|
||||
unsafe { IPCConnector::from_raw_ancillary(pipe) }
|
||||
}
|
||||
|
||||
/// Serialize this connector into a string that can be passed on the
|
||||
|
|
@ -178,149 +207,115 @@ impl IPCConnector {
|
|||
pub fn deserialize(string: &CStr) -> Result<IPCConnector, IPCError> {
|
||||
let string = string.to_str().map_err(|_e| IPCError::ParseError)?;
|
||||
let handle = usize::from_str(string).map_err(|_e| IPCError::ParseError)?;
|
||||
let handle = handle as *mut c_void;
|
||||
// SAFETY: This is a handle we passed in ourselves.
|
||||
let handle = unsafe { OwnedHandle::from_raw_handle(handle) };
|
||||
IPCConnector::new(handle)
|
||||
unsafe { IPCConnector::from_raw_ancillary(handle as HANDLE) }
|
||||
}
|
||||
|
||||
pub fn into_ancillary(
|
||||
self,
|
||||
dst_process: &Option<ProcessHandle>,
|
||||
) -> Result<AncillaryData, IPCError> {
|
||||
let mut dst_handle: HANDLE = INVALID_ANCILLARY_DATA;
|
||||
|
||||
if let Some(dst_process) = dst_process.as_ref() {
|
||||
let res = unsafe {
|
||||
DuplicateHandle(
|
||||
GetCurrentProcess(),
|
||||
self.handle.into_raw_handle() as HANDLE,
|
||||
dst_process.as_raw_handle() as HANDLE,
|
||||
&mut dst_handle,
|
||||
/* dwDesiredAccess */ 0,
|
||||
/* bInheritHandle */ FALSE,
|
||||
DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS,
|
||||
)
|
||||
};
|
||||
|
||||
if res > 0 {
|
||||
Ok(dst_handle)
|
||||
} else {
|
||||
Err(IPCError::System(get_last_error()))
|
||||
}
|
||||
} else {
|
||||
Ok(self.handle.into_raw_handle() as HANDLE)
|
||||
}
|
||||
pub fn into_ancillary(self) -> AncillaryData {
|
||||
Rc::try_unwrap(self.handle).expect("Multiple references to the underlying handle")
|
||||
}
|
||||
|
||||
/// Like into_ancillary, but the IPCConnector retains ownership of the file descriptor (so be
|
||||
/// sure to use the result during the lifetime of the IPCConnector).
|
||||
pub fn as_ancillary(
|
||||
&self,
|
||||
dst_process: &Option<ProcessHandle>,
|
||||
) -> Result<AncillaryData, IPCError> {
|
||||
let mut dst_handle: HANDLE = INVALID_ANCILLARY_DATA;
|
||||
|
||||
if let Some(dst_process) = dst_process.as_ref() {
|
||||
let res = unsafe {
|
||||
DuplicateHandle(
|
||||
GetCurrentProcess(),
|
||||
self.handle.as_raw_handle() as HANDLE,
|
||||
dst_process.as_raw_handle() as HANDLE,
|
||||
&mut dst_handle,
|
||||
/* dwDesiredAccess */ 0,
|
||||
/* bInheritHandle */ FALSE,
|
||||
DUPLICATE_SAME_ACCESS,
|
||||
)
|
||||
};
|
||||
|
||||
if res > 0 {
|
||||
Ok(dst_handle)
|
||||
} else {
|
||||
Err(IPCError::System(get_last_error()))
|
||||
}
|
||||
} else {
|
||||
Ok(self.handle.as_raw_handle() as HANDLE)
|
||||
}
|
||||
pub fn into_raw_ancillary(self) -> RawAncillaryData {
|
||||
self.into_ancillary().into_raw()
|
||||
}
|
||||
|
||||
pub fn send_message(&self, message: &dyn Message) -> Result<(), IPCError> {
|
||||
pub fn send_message<T>(&self, message: T) -> Result<(), IPCError>
|
||||
where
|
||||
T: Message,
|
||||
{
|
||||
self.send_message_internal(message)
|
||||
.map_err(IPCError::TransmissionFailure)
|
||||
}
|
||||
|
||||
fn send_message_internal<T>(&self, message: T) -> Result<(), PlatformError>
|
||||
where
|
||||
T: Message,
|
||||
{
|
||||
let header = message.header();
|
||||
let (payload, ancillary_data) = message.into_payload();
|
||||
|
||||
// Send the message header
|
||||
self.send(&message.header(), None)?;
|
||||
OverlappedOperation::send(&self.handle, self.event.as_handle(), header)?;
|
||||
|
||||
// Send the message payload
|
||||
self.send(&message.payload(), message.ancillary_payload())?;
|
||||
// Send the message payload plus the optional handles
|
||||
let handle = if let Some(handle) = ancillary_data {
|
||||
self.clone_handle(handle)?
|
||||
} else {
|
||||
INVALID_ANCILLARY_DATA
|
||||
};
|
||||
|
||||
Ok(())
|
||||
let mut buffer = Vec::<u8>::with_capacity(HANDLE_SIZE + payload.len());
|
||||
buffer.extend(handle.to_ne_bytes());
|
||||
buffer.extend(payload);
|
||||
|
||||
OverlappedOperation::send(&self.handle, self.event.as_handle(), buffer)
|
||||
}
|
||||
|
||||
pub fn recv_reply<T>(&self) -> Result<T, IPCError>
|
||||
where
|
||||
T: Message,
|
||||
{
|
||||
let header = self.recv_header()?;
|
||||
let header = self
|
||||
.recv_buffer(messages::HEADER_SIZE)
|
||||
.map_err(IPCError::ReceptionFailure)?;
|
||||
let header = messages::Header::decode(&header).map_err(IPCError::BadMessage)?;
|
||||
|
||||
if header.kind != T::kind() {
|
||||
return Err(IPCError::ReceptionFailure(ERROR_INVALID_MESSAGE));
|
||||
return Err(IPCError::UnexpectedMessage(header.kind));
|
||||
}
|
||||
|
||||
let (data, _) = self.recv(header.size)?;
|
||||
T::decode(&data, None).map_err(IPCError::from)
|
||||
let (buffer, handle) = self.recv(header.size)?;
|
||||
T::decode(&buffer, handle).map_err(IPCError::from)
|
||||
}
|
||||
|
||||
fn recv_header(&self) -> Result<messages::Header, IPCError> {
|
||||
let (header, _) = self.recv(messages::HEADER_SIZE)?;
|
||||
messages::Header::decode(&header).map_err(IPCError::BadMessage)
|
||||
pub(crate) fn sched_recv_header(&self) -> Result<OverlappedOperation, IPCError> {
|
||||
OverlappedOperation::sched_recv(&self.handle, HEADER_SIZE)
|
||||
.map_err(IPCError::ReceptionFailure)
|
||||
}
|
||||
|
||||
pub fn sched_recv_header(&mut self) -> Result<(), IPCError> {
|
||||
if self.overlapped.is_some() {
|
||||
// We're already waiting for a header.
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
self.overlapped = Some(OverlappedOperation::sched_recv(
|
||||
self.handle
|
||||
.try_clone()
|
||||
.map_err(IPCError::CloneHandleFailed)?,
|
||||
self.event_raw_handle(),
|
||||
HANDLE_SIZE + messages::HEADER_SIZE,
|
||||
)?);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn collect_header(&mut self) -> Result<messages::Header, IPCError> {
|
||||
// We should never call collect_header() on a connector that wasn't
|
||||
// waiting for one, so panic in that scenario.
|
||||
let overlapped = self.overlapped.take().unwrap();
|
||||
let buffer = overlapped.collect_recv(/* wait */ false)?;
|
||||
let (data, _) = extract_buffer_and_handle(buffer)?;
|
||||
messages::Header::decode(data.as_ref()).map_err(IPCError::BadMessage)
|
||||
}
|
||||
|
||||
pub fn send(&self, buff: &[u8], handle: Option<AncillaryData>) -> Result<(), IPCError> {
|
||||
let mut buffer = Vec::<u8>::with_capacity(HANDLE_SIZE + buff.len());
|
||||
buffer.extend(handle.unwrap_or(INVALID_ANCILLARY_DATA).to_ne_bytes());
|
||||
buffer.extend(buff);
|
||||
|
||||
let overlapped =
|
||||
OverlappedOperation::sched_send(self.handle
|
||||
.try_clone()
|
||||
.map_err(IPCError::CloneHandleFailed)?, self.event_raw_handle(), buffer)?;
|
||||
overlapped.complete_send(/* wait */ true)
|
||||
}
|
||||
|
||||
pub fn recv(&self, expected_size: usize) -> Result<(Vec<u8>, Option<AncillaryData>), IPCError> {
|
||||
let overlapped = OverlappedOperation::sched_recv(
|
||||
self.handle
|
||||
.try_clone()
|
||||
.map_err(IPCError::CloneHandleFailed)?,
|
||||
self.event_raw_handle(),
|
||||
HANDLE_SIZE + expected_size,
|
||||
)?;
|
||||
let buffer = overlapped.collect_recv(/* wait */ true)?;
|
||||
pub(crate) fn recv(
|
||||
&self,
|
||||
expected_size: usize,
|
||||
) -> Result<(Vec<u8>, Option<AncillaryData>), IPCError> {
|
||||
let buffer = self
|
||||
.recv_buffer(HANDLE_SIZE + expected_size)
|
||||
.map_err(IPCError::ReceptionFailure)?;
|
||||
extract_buffer_and_handle(buffer)
|
||||
}
|
||||
|
||||
fn recv_buffer(&self, expected_size: usize) -> Result<Vec<u8>, PlatformError> {
|
||||
OverlappedOperation::recv(&self.handle, self.event.as_handle(), expected_size)
|
||||
}
|
||||
|
||||
/// Clone a handle in the destination process, this is required to
|
||||
/// transfer handles over this connector. Note that this consumes the
|
||||
/// incoming handle because we want it to be closed after it's been cloned
|
||||
/// over to the other process.
|
||||
fn clone_handle(&self, handle: OwnedHandle) -> Result<HANDLE, PlatformError> {
|
||||
let Some(dst_process) = self.process.as_ref() else {
|
||||
return Err(PlatformError::MissingProcessHandle);
|
||||
};
|
||||
let mut dst_handle: HANDLE = INVALID_ANCILLARY_DATA;
|
||||
let res = unsafe {
|
||||
DuplicateHandle(
|
||||
GetCurrentProcess(),
|
||||
handle.into_raw_handle() as HANDLE,
|
||||
dst_process.as_raw_handle() as HANDLE,
|
||||
&mut dst_handle,
|
||||
/* dwDesiredAccess */ 0,
|
||||
/* bInheritHandle */ FALSE,
|
||||
DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS,
|
||||
)
|
||||
};
|
||||
|
||||
if res == 0 {
|
||||
return Err(PlatformError::CloneHandleFailed(Error::from_raw_os_error(
|
||||
get_last_error() as i32,
|
||||
)));
|
||||
}
|
||||
|
||||
Ok(dst_handle)
|
||||
}
|
||||
}
|
||||
|
||||
// SAFETY: The connector can be transferred across threads in spite of the raw
|
||||
|
|
|
|||
|
|
@ -2,6 +2,30 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::{errors::IPCError, platform::PlatformError};
|
||||
|
||||
/*****************************************************************************
|
||||
* Error definitions *
|
||||
*****************************************************************************/
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum IPCListenerError {
|
||||
#[error("Could not accept incoming connection: {0}")]
|
||||
AcceptError(PlatformError),
|
||||
#[error("Issue with an underlying connector: {0}")]
|
||||
ConnectorError(#[from] IPCError),
|
||||
#[error("Could not create listener: {0}")]
|
||||
CreationError(PlatformError),
|
||||
#[error("Could not listen for incoming connections: {0}")]
|
||||
ListenError(PlatformError),
|
||||
#[error("Could not parse handle: {0}")]
|
||||
ParseError(String),
|
||||
#[error("Could not create a new pipe: {0}")]
|
||||
PipeCreationFailure(PlatformError),
|
||||
}
|
||||
|
||||
/*****************************************************************************
|
||||
* Windows *
|
||||
*****************************************************************************/
|
||||
|
|
|
|||
|
|
@ -4,18 +4,21 @@
|
|||
|
||||
use crate::{
|
||||
errors::IPCError,
|
||||
platform::windows::{create_manual_reset_event, server_addr, OverlappedOperation},
|
||||
ipc_listener::IPCListenerError,
|
||||
platform::windows::{get_last_error, server_addr, OverlappedOperation, PlatformError},
|
||||
IPCConnector, Pid,
|
||||
};
|
||||
|
||||
use std::{
|
||||
ffi::{c_void, CStr, CString, OsString},
|
||||
cell::RefCell,
|
||||
ffi::{CStr, CString, OsString},
|
||||
os::windows::io::{AsRawHandle, FromRawHandle, OwnedHandle, RawHandle},
|
||||
ptr::null_mut,
|
||||
rc::Rc,
|
||||
str::FromStr,
|
||||
};
|
||||
use windows_sys::Win32::{
|
||||
Foundation::{GetLastError, HANDLE, INVALID_HANDLE_VALUE, TRUE},
|
||||
Foundation::{HANDLE, INVALID_HANDLE_VALUE, TRUE},
|
||||
Security::SECURITY_ATTRIBUTES,
|
||||
Storage::FileSystem::{
|
||||
FILE_FLAG_FIRST_PIPE_INSTANCE, FILE_FLAG_OVERLAPPED, PIPE_ACCESS_DUPLEX,
|
||||
|
|
@ -27,90 +30,86 @@ use windows_sys::Win32::{
|
|||
};
|
||||
|
||||
pub struct IPCListener {
|
||||
/// The name of the pipe this listener will be bound to
|
||||
server_addr: CString,
|
||||
handle: OwnedHandle,
|
||||
/// A named pipe handle listening for incoming connections
|
||||
handle: RefCell<Rc<OwnedHandle>>,
|
||||
/// Stores the only listen operation that might be pending
|
||||
overlapped: Option<OverlappedOperation>,
|
||||
event: OwnedHandle,
|
||||
}
|
||||
|
||||
impl IPCListener {
|
||||
pub fn new(server_addr: CString) -> Result<IPCListener, IPCError> {
|
||||
let pipe = create_named_pipe(&server_addr, /* first_instance */ true)?;
|
||||
let event = create_manual_reset_event()?;
|
||||
pub(crate) fn new(server_addr: CString) -> Result<IPCListener, IPCListenerError> {
|
||||
let pipe = create_named_pipe(&server_addr, /* first_instance */ true)
|
||||
.map_err(IPCListenerError::PipeCreationFailure)?;
|
||||
|
||||
Ok(IPCListener {
|
||||
server_addr,
|
||||
handle: pipe,
|
||||
handle: RefCell::new(Rc::new(pipe)),
|
||||
overlapped: None,
|
||||
event,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn event_raw_handle(&self) -> HANDLE {
|
||||
self.event.as_raw_handle() as HANDLE
|
||||
pub(crate) fn as_raw(&self) -> HANDLE {
|
||||
self.handle.borrow().as_raw_handle() as HANDLE
|
||||
}
|
||||
|
||||
pub fn address(&self) -> &CStr {
|
||||
pub(crate) fn address(&self) -> &CStr {
|
||||
&self.server_addr
|
||||
}
|
||||
|
||||
pub fn listen(&mut self) -> Result<(), IPCError> {
|
||||
self.overlapped = Some(OverlappedOperation::listen(
|
||||
self.handle
|
||||
.try_clone()
|
||||
.map_err(IPCError::CloneHandleFailed)?,
|
||||
self.event_raw_handle(),
|
||||
)?);
|
||||
pub(crate) fn sched_listen(&self) -> Result<OverlappedOperation, IPCListenerError> {
|
||||
OverlappedOperation::listen(&self.handle.borrow()).map_err(IPCListenerError::ListenError)
|
||||
}
|
||||
|
||||
pub(crate) fn listen(&mut self) -> Result<(), IPCListenerError> {
|
||||
self.overlapped = Some(self.sched_listen()?);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn accept(&mut self) -> Result<IPCConnector, IPCError> {
|
||||
// We should never call accept() on a listener that wasn't
|
||||
// already waiting, so panic in that scenario.
|
||||
let overlapped = self.overlapped.take().unwrap();
|
||||
overlapped.accept(self.handle.as_raw_handle() as HANDLE)?;
|
||||
let new_pipe = create_named_pipe(&self.server_addr, /* first_instance */ false)?;
|
||||
let connected_pipe = std::mem::replace(&mut self.handle, new_pipe);
|
||||
pub fn accept(&mut self) -> Result<IPCConnector, IPCListenerError> {
|
||||
let overlapped = self
|
||||
.overlapped
|
||||
.take()
|
||||
.expect("Accepting a connection without listening first");
|
||||
overlapped.accept().map_err(IPCListenerError::AcceptError)?;
|
||||
self.replace_pipe()
|
||||
}
|
||||
|
||||
// Once we've accepted a new connection and replaced the listener's
|
||||
// pipe we need to listen again before we return, so that we're ready
|
||||
// for the next iteration.
|
||||
self.listen()?;
|
||||
pub(crate) fn replace_pipe(&self) -> Result<IPCConnector, IPCListenerError> {
|
||||
let new_pipe = create_named_pipe(&self.server_addr, /* first_instance */ false)
|
||||
.map_err(IPCListenerError::PipeCreationFailure)?;
|
||||
let connected_pipe = self.handle.replace(Rc::new(new_pipe));
|
||||
|
||||
IPCConnector::new(connected_pipe)
|
||||
// We can guarantee that there's only one reference to this handle at
|
||||
// this point in time.
|
||||
Ok(IPCConnector::from_ancillary(
|
||||
Rc::<OwnedHandle>::try_unwrap(connected_pipe).unwrap(),
|
||||
)?)
|
||||
}
|
||||
|
||||
/// Serialize this listener into a string that can be passed on the
|
||||
/// command-line to a child process. This only works for newly
|
||||
/// created listeners because they are explicitly created as inheritable.
|
||||
pub fn serialize(&self) -> OsString {
|
||||
let raw_handle = self.handle.as_raw_handle() as usize;
|
||||
let raw_handle = self.handle.borrow().as_raw_handle() as usize;
|
||||
OsString::from_str(raw_handle.to_string().as_ref()).unwrap()
|
||||
}
|
||||
|
||||
/// Deserialize a listener from an argument passed on the command-line.
|
||||
/// The resulting listener is ready to accept new connections.
|
||||
pub fn deserialize(string: &CStr, pid: Pid) -> Result<IPCListener, IPCError> {
|
||||
pub fn deserialize(string: &CStr, pid: Pid) -> Result<IPCListener, IPCListenerError> {
|
||||
let server_addr = server_addr(pid);
|
||||
let string = string.to_str().map_err(|_e| IPCError::ParseError)?;
|
||||
let handle = usize::from_str(string).map_err(|_e| IPCError::ParseError)?;
|
||||
let handle = handle as *mut c_void;
|
||||
// SAFETY: This is a handle we passed in ourselves.
|
||||
let handle = unsafe { OwnedHandle::from_raw_handle(handle) };
|
||||
let event = create_manual_reset_event()?;
|
||||
let handle = unsafe { OwnedHandle::from_raw_handle(handle as RawHandle) };
|
||||
|
||||
let mut listener = IPCListener {
|
||||
Ok(IPCListener {
|
||||
server_addr,
|
||||
handle,
|
||||
handle: RefCell::new(Rc::new(handle)),
|
||||
overlapped: None,
|
||||
event,
|
||||
};
|
||||
|
||||
// Since we've inherited this handler we need to start a new
|
||||
// asynchronous operation to listen for incoming connections.
|
||||
listener.listen()?;
|
||||
|
||||
Ok(listener)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -119,7 +118,10 @@ impl IPCListener {
|
|||
// used internally and never visible externally.
|
||||
unsafe impl Send for IPCListener {}
|
||||
|
||||
fn create_named_pipe(server_addr: &CStr, first_instance: bool) -> Result<OwnedHandle, IPCError> {
|
||||
fn create_named_pipe(
|
||||
server_addr: &CStr,
|
||||
first_instance: bool,
|
||||
) -> Result<OwnedHandle, PlatformError> {
|
||||
const PIPE_BUFFER_SIZE: u32 = 4096;
|
||||
|
||||
let open_mode = PIPE_ACCESS_DUPLEX
|
||||
|
|
@ -152,7 +154,7 @@ fn create_named_pipe(server_addr: &CStr, first_instance: bool) -> Result<OwnedHa
|
|||
};
|
||||
|
||||
if pipe == INVALID_HANDLE_VALUE {
|
||||
return Err(IPCError::System(unsafe { GetLastError() }));
|
||||
return Err(PlatformError::CreatePipeFailure(get_last_error()));
|
||||
}
|
||||
|
||||
// SAFETY: We just verified that the handle is valid.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,53 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::{
|
||||
errors::{IPCError, SystemError},
|
||||
messages::MessageError,
|
||||
IPCListenerError,
|
||||
};
|
||||
|
||||
/*****************************************************************************
|
||||
* Error definitions *
|
||||
*****************************************************************************/
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum IPCQueueError {
|
||||
#[error("Could not create queue: {0}")]
|
||||
CreationFailure(SystemError),
|
||||
#[error("Could not register with queue: {0}")]
|
||||
RegistrationFailure(SystemError),
|
||||
#[error("Could not post an event on the queue: {0}")]
|
||||
PostEventFailure(SystemError),
|
||||
#[error("Could not wait for events: {0}")]
|
||||
WaitError(SystemError),
|
||||
#[error("Underlying IPC connector error: {0}")]
|
||||
IPCError(#[from] IPCError),
|
||||
#[error("Underlying IPC listener error: {0}")]
|
||||
IPCListenerError(#[from] IPCListenerError),
|
||||
#[error("Underlying message error: {0}")]
|
||||
MessageError(#[from] MessageError),
|
||||
}
|
||||
|
||||
/*****************************************************************************
|
||||
* Windows *
|
||||
*****************************************************************************/
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
pub use windows::IPCQueue;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
pub(crate) mod windows;
|
||||
|
||||
/*****************************************************************************
|
||||
* Android, macOS & Linux *
|
||||
*****************************************************************************/
|
||||
|
||||
#[cfg(any(target_os = "android", target_os = "linux", target_os = "macos"))]
|
||||
pub use unix::IPCQueue;
|
||||
|
||||
#[cfg(any(target_os = "android", target_os = "linux", target_os = "macos"))]
|
||||
pub(crate) mod unix;
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use nix::poll::{poll, PollFd, PollFlags, PollTimeout};
|
||||
use std::{collections::HashMap, os::fd::BorrowedFd, rc::Rc};
|
||||
|
||||
use crate::{
|
||||
ignore_eintr, ipc_queue::IPCQueueError, IPCConnector, IPCConnectorKey, IPCEvent, IPCListener,
|
||||
};
|
||||
|
||||
pub struct IPCQueue {
|
||||
connectors: HashMap<IPCConnectorKey, Rc<IPCConnector>>,
|
||||
}
|
||||
|
||||
impl IPCQueue {
|
||||
pub fn new(_listener: IPCListener) -> Result<IPCQueue, IPCQueueError> {
|
||||
let connectors = HashMap::with_capacity(10);
|
||||
Ok(IPCQueue { connectors })
|
||||
}
|
||||
|
||||
pub fn add_connector(&mut self, connector: &Rc<IPCConnector>) -> Result<(), IPCQueueError> {
|
||||
let res = self.connectors.insert(connector.key(), connector.clone());
|
||||
debug_assert!(res.is_none());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn add_listener(&self, _listener: &IPCListener) -> Result<(), IPCQueueError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn wait_for_events(&mut self) -> Result<Vec<IPCEvent>, IPCQueueError> {
|
||||
let mut pollfds = Vec::with_capacity(self.connectors.len());
|
||||
// SAFETY: All the fds held by the queue are known to be valid.
|
||||
pollfds.extend(self.connectors.iter().map(|connector| {
|
||||
PollFd::new(
|
||||
unsafe { BorrowedFd::borrow_raw(connector.1.as_raw()) },
|
||||
PollFlags::POLLIN,
|
||||
)
|
||||
}));
|
||||
|
||||
let mut events = Vec::<IPCEvent>::new();
|
||||
let mut num_events = ignore_eintr!(poll(&mut pollfds, PollTimeout::NONE))
|
||||
.map_err(IPCQueueError::WaitError)?;
|
||||
|
||||
for (pollfd, (&key, connector)) in pollfds.iter().zip(&self.connectors) {
|
||||
// revents() returns None only if the kernel sends back data
|
||||
// that nix does not understand, we can safely assume this
|
||||
// never happens in practice hence the unwrap().
|
||||
let Some(revents) = pollfd.revents() else {
|
||||
// TODO: We should log this error, disconnect the socket or do
|
||||
// both things. Probably needs a new event type.
|
||||
continue;
|
||||
};
|
||||
|
||||
if revents.contains(PollFlags::POLLHUP) {
|
||||
events.push(IPCEvent::Disconnect(key));
|
||||
// If a process was disconnected then skip all further
|
||||
// processing of the socket. This wouldn't matter normally,
|
||||
// but on macOS calling recvmsg() on a hung-up socket seems
|
||||
// to trigger a kernel panic, one we've already encountered
|
||||
// in the past. Doing things this way avoids the panic
|
||||
// while having no real downsides.
|
||||
continue;
|
||||
}
|
||||
|
||||
if revents.contains(PollFlags::POLLIN) {
|
||||
let header = connector.recv_header()?;
|
||||
let payload = connector
|
||||
.recv(header.size)
|
||||
.map_err(IPCQueueError::IPCError)?;
|
||||
events.push(IPCEvent::Message(key, header, payload.0, payload.1));
|
||||
}
|
||||
|
||||
if !revents.is_empty() {
|
||||
num_events -= 1;
|
||||
|
||||
if num_events == 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove all connectors for which we've received disconnect events.
|
||||
for event in &events {
|
||||
if let IPCEvent::Disconnect(key) = event {
|
||||
self.connectors.remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(events)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,281 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
mem::MaybeUninit,
|
||||
os::windows::io::{AsRawHandle, FromRawHandle, OwnedHandle, RawHandle},
|
||||
ptr::null_mut,
|
||||
rc::Rc,
|
||||
};
|
||||
use windows_sys::Win32::{
|
||||
Foundation::{ERROR_BROKEN_PIPE, FALSE, HANDLE, INVALID_HANDLE_VALUE},
|
||||
System::{
|
||||
Threading::INFINITE,
|
||||
IO::{CreateIoCompletionPort, GetQueuedCompletionStatus, OVERLAPPED},
|
||||
},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
errors::IPCError,
|
||||
ipc_queue::IPCQueueError,
|
||||
messages::Header,
|
||||
platform::{
|
||||
windows::{get_last_error, OverlappedOperation},
|
||||
PlatformError,
|
||||
},
|
||||
IPCConnector, IPCConnectorKey, IPCEvent, IPCListener,
|
||||
};
|
||||
|
||||
const CONCURRENT_THREADS: u32 = 1;
|
||||
|
||||
struct IPCQueueElement {
|
||||
connector: Rc<IPCConnector>,
|
||||
operation: Option<OverlappedOperation>,
|
||||
}
|
||||
|
||||
pub struct IPCQueue {
|
||||
connectors: HashMap<IPCConnectorKey, IPCQueueElement>,
|
||||
listener: IPCListener,
|
||||
listen_operation: Option<OverlappedOperation>,
|
||||
port: OwnedHandle,
|
||||
}
|
||||
|
||||
impl IPCQueue {
|
||||
pub fn new(listener: IPCListener) -> Result<IPCQueue, IPCQueueError> {
|
||||
let listener_port = listener.as_raw();
|
||||
|
||||
// Create a new completion port that allows only one active thread.
|
||||
let port = unsafe {
|
||||
CreateIoCompletionPort(
|
||||
/* FileHandle */ INVALID_HANDLE_VALUE,
|
||||
/* ExistingCompletionPort */ 0,
|
||||
/* CompletionKey */ 0,
|
||||
CONCURRENT_THREADS,
|
||||
) as RawHandle
|
||||
};
|
||||
|
||||
if port.is_null() {
|
||||
return Err(IPCQueueError::CreationFailure(get_last_error()));
|
||||
}
|
||||
|
||||
let mut queue = IPCQueue {
|
||||
connectors: HashMap::with_capacity(10),
|
||||
listener,
|
||||
listen_operation: None,
|
||||
port: unsafe { OwnedHandle::from_raw_handle(port) },
|
||||
};
|
||||
|
||||
queue.add_handle(listener_port)?;
|
||||
|
||||
Ok(queue)
|
||||
}
|
||||
|
||||
pub fn add_connector(&mut self, connector: &Rc<IPCConnector>) -> Result<(), IPCQueueError> {
|
||||
self.add_handle(connector.as_raw())?;
|
||||
self.insert_connector(connector);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn insert_connector(&mut self, connector: &Rc<IPCConnector>) {
|
||||
let res = self.connectors.insert(
|
||||
connector.key(),
|
||||
IPCQueueElement {
|
||||
connector: connector.clone(),
|
||||
operation: None,
|
||||
},
|
||||
);
|
||||
debug_assert!(res.is_none());
|
||||
}
|
||||
|
||||
fn add_handle(&mut self, handle: HANDLE) -> Result<(), IPCQueueError> {
|
||||
let port = unsafe {
|
||||
CreateIoCompletionPort(
|
||||
handle,
|
||||
self.port.as_raw_handle() as HANDLE,
|
||||
// Use the connector's handle as the events' key
|
||||
handle as usize,
|
||||
CONCURRENT_THREADS,
|
||||
) as RawHandle
|
||||
};
|
||||
|
||||
if port.is_null() {
|
||||
return Err(IPCQueueError::RegistrationFailure(get_last_error()));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn wait_for_events(&mut self) -> Result<Vec<IPCEvent>, IPCQueueError> {
|
||||
let mut events = Vec::with_capacity(1);
|
||||
|
||||
for element in self.connectors.values_mut() {
|
||||
if element.operation.is_none() {
|
||||
match element.connector.sched_recv_header() {
|
||||
Ok(operation) => element.operation = Some(operation),
|
||||
Err(_error @ IPCError::ReceptionFailure(PlatformError::BrokenPipe)) => {
|
||||
events.push(IPCEvent::Disconnect(element.connector.key()));
|
||||
}
|
||||
Err(error) => return Err(IPCQueueError::from(error)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for event in &events {
|
||||
if let IPCEvent::Disconnect(key) = event {
|
||||
self.connectors.remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
if self.connectors.len() == 0 {
|
||||
// The last client disconnected.
|
||||
return Ok(events);
|
||||
}
|
||||
|
||||
if self.listen_operation.is_none() {
|
||||
self.listen_operation = Some(self.listener.sched_listen()?);
|
||||
}
|
||||
|
||||
let mut number_of_bytes_transferred = MaybeUninit::<u32>::uninit();
|
||||
let mut completion_key = MaybeUninit::<IPCConnectorKey>::uninit();
|
||||
let mut overlapped = MaybeUninit::<*mut OVERLAPPED>::uninit();
|
||||
|
||||
let res = unsafe {
|
||||
GetQueuedCompletionStatus(
|
||||
self.port.as_raw_handle() as HANDLE,
|
||||
number_of_bytes_transferred.as_mut_ptr(),
|
||||
completion_key.as_mut_ptr(),
|
||||
overlapped.as_mut_ptr(),
|
||||
INFINITE,
|
||||
)
|
||||
};
|
||||
|
||||
// SAFETY: `overlapped` will always be populated by
|
||||
// `GetQueueCompletionStatus()` so it's safe to assume initialization.
|
||||
let overlapped = unsafe { overlapped.assume_init() };
|
||||
|
||||
if res == FALSE {
|
||||
let err = get_last_error();
|
||||
|
||||
// If `overlapped` is non-null then the completion packet contained
|
||||
// the result of a failed I/O operation. We only handle failures
|
||||
// caused by a broken pipes, all others are considered fatal.
|
||||
if !overlapped.is_null() && (err == ERROR_BROKEN_PIPE) {
|
||||
// SAFETY: `overlapped` was non-null, so `completion_key` has
|
||||
// also been populated by `GetQueuedCompletionStatus()`.
|
||||
let completion_key = unsafe { completion_key.assume_init() };
|
||||
let element = self.connectors.remove(&completion_key);
|
||||
debug_assert!(element.is_some(), "Completion on missing connector");
|
||||
events.push(IPCEvent::Disconnect(completion_key));
|
||||
} else {
|
||||
return Err(IPCQueueError::WaitError(err));
|
||||
}
|
||||
} else {
|
||||
// SAFETY: `GetQueueCompletionStatus()` successfully retrieved a
|
||||
// completed I/O operation, all parameters have been populated.
|
||||
let (number_of_bytes_transferred, completion_key) = unsafe {
|
||||
(
|
||||
number_of_bytes_transferred.assume_init(),
|
||||
completion_key.assume_init(),
|
||||
)
|
||||
};
|
||||
|
||||
if number_of_bytes_transferred == 0 {
|
||||
// This is an event on the listener
|
||||
debug_assert!(
|
||||
self.listener.as_raw() as IPCConnectorKey == completion_key,
|
||||
"Completion event doesn't match the listener"
|
||||
);
|
||||
let operation = self.listen_operation.take();
|
||||
if let Some(operation) = operation {
|
||||
operation
|
||||
.accept()
|
||||
.map_err(|_e| IPCQueueError::RegistrationFailure(0))?;
|
||||
}
|
||||
let connector = Rc::new(self.listener.replace_pipe()?);
|
||||
self.insert_connector(&connector);
|
||||
|
||||
// After the pipe is connected the listener handle will have been
|
||||
// replaced with a new one, so associate the new handle with the
|
||||
// completion queue.
|
||||
self.add_handle(self.listener.as_raw())?;
|
||||
|
||||
events.push(IPCEvent::Connect(connector));
|
||||
} else {
|
||||
let element = self
|
||||
.connectors
|
||||
.get_mut(&completion_key)
|
||||
.expect("Event did not match a known connector");
|
||||
let operation = element
|
||||
.operation
|
||||
.take()
|
||||
.expect("No pending receive operation");
|
||||
let buffer = &operation.collect_recv();
|
||||
let header = Header::decode(buffer)?;
|
||||
let payload = element.connector.recv(header.size);
|
||||
match payload {
|
||||
Ok(payload) => {
|
||||
events.push(IPCEvent::Message(
|
||||
completion_key,
|
||||
header,
|
||||
payload.0,
|
||||
payload.1,
|
||||
));
|
||||
}
|
||||
Err(_error @ IPCError::ReceptionFailure(PlatformError::BrokenPipe)) => {
|
||||
// This connector will generate a disconnection event
|
||||
// when `wait_for_events()` is called again. Do nothing
|
||||
// for the time being.
|
||||
}
|
||||
Err(error) => return Err(IPCQueueError::from(error)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(events)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for IPCQueue {
|
||||
fn drop(&mut self) {
|
||||
// Cancel all the pending operations.
|
||||
for element in self.connectors.values_mut() {
|
||||
if let Some(operation) = &mut element.operation {
|
||||
if !operation.cancel() {
|
||||
operation.leak();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(operation) = &mut self.listen_operation {
|
||||
if !operation.cancel() {
|
||||
operation.leak();
|
||||
}
|
||||
}
|
||||
|
||||
// Drain the queue, once no more events are left we can safely drop it.
|
||||
loop {
|
||||
let mut number_of_bytes_transferred: u32 = 0;
|
||||
let mut completion_key: IPCConnectorKey = 0;
|
||||
let mut overlapped: *mut OVERLAPPED = null_mut();
|
||||
|
||||
let res = unsafe {
|
||||
GetQueuedCompletionStatus(
|
||||
self.port.as_raw_handle() as HANDLE,
|
||||
&mut number_of_bytes_transferred,
|
||||
&mut completion_key,
|
||||
&mut overlapped,
|
||||
0,
|
||||
)
|
||||
};
|
||||
|
||||
// TODO: Check that we got enough completion events?
|
||||
|
||||
if res == FALSE && overlapped.is_null() {
|
||||
// TODO: Maybe check the error and report odd ones?
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -11,15 +11,20 @@ mod breakpad;
|
|||
mod ipc_channel;
|
||||
mod ipc_connector;
|
||||
mod ipc_listener;
|
||||
mod ipc_queue;
|
||||
mod platform;
|
||||
|
||||
use errors::MessageError;
|
||||
use messages::MessageError;
|
||||
|
||||
// Re-export the platform-specific types and functions
|
||||
pub use crate::breakpad::{BreakpadChar, BreakpadData, BreakpadRawData, Pid};
|
||||
pub use crate::ipc_channel::{IPCChannel, IPCClientChannel};
|
||||
pub use crate::ipc_connector::{AncillaryData, IPCConnector, IPCEvent, INVALID_ANCILLARY_DATA};
|
||||
pub use crate::ipc_listener::IPCListener;
|
||||
pub use crate::ipc_connector::{
|
||||
AncillaryData, IPCConnector, IPCConnectorKey, IPCEvent, RawAncillaryData,
|
||||
INVALID_ANCILLARY_DATA,
|
||||
};
|
||||
pub use crate::ipc_listener::{IPCListener, IPCListenerError};
|
||||
pub use crate::ipc_queue::IPCQueue;
|
||||
pub use crate::platform::ProcessHandle;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
|
|
@ -58,4 +63,9 @@ pub trait BreakpadString {
|
|||
unsafe fn from_raw(ptr: *mut BreakpadChar) -> OsString;
|
||||
}
|
||||
|
||||
/// Owned handle or file descriptor conversion to their respective raw versions
|
||||
pub trait IntoRawAncillaryData {
|
||||
fn into_raw(self) -> RawAncillaryData;
|
||||
}
|
||||
|
||||
pub const IO_TIMEOUT: u16 = 2 * 1000;
|
||||
|
|
|
|||
|
|
@ -7,13 +7,33 @@ use minidump_writer::minidump_writer::{AuxvType, DirectAuxvDumpInfo};
|
|||
use num_derive::{FromPrimitive, ToPrimitive};
|
||||
use num_traits::FromPrimitive;
|
||||
use std::{
|
||||
ffi::{CString, OsString},
|
||||
array::TryFromSliceError,
|
||||
ffi::{CString, FromBytesWithNulError, NulError, OsString},
|
||||
mem::size_of,
|
||||
};
|
||||
use thiserror::Error;
|
||||
#[cfg(target_os = "windows")]
|
||||
use windows_sys::Win32::System::Diagnostics::Debug::{CONTEXT, EXCEPTION_RECORD};
|
||||
|
||||
use crate::{breakpad::Pid, errors::MessageError, ipc_connector::AncillaryData, BreakpadString};
|
||||
use crate::{breakpad::Pid, ipc_connector::AncillaryData, BreakpadString};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum MessageError {
|
||||
#[error("Nul terminator found within a string")]
|
||||
InteriorNul(#[from] NulError),
|
||||
#[error("The message contained an invalid payload")]
|
||||
InvalidData,
|
||||
#[error("Message kind is invalid")]
|
||||
InvalidKind,
|
||||
#[error("Invalid message size")]
|
||||
InvalidSize(#[from] TryFromSliceError),
|
||||
#[error("Missing ancillary data")]
|
||||
MissingAncillary,
|
||||
#[error("Missing nul terminator")]
|
||||
MissingNul(#[from] FromBytesWithNulError),
|
||||
#[error("Truncated message")]
|
||||
Truncated,
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Copy, Clone, Debug, FromPrimitive, ToPrimitive, PartialEq)]
|
||||
|
|
@ -57,8 +77,7 @@ pub trait Message {
|
|||
where
|
||||
Self: Sized;
|
||||
fn header(&self) -> Vec<u8>;
|
||||
fn payload(&self) -> Vec<u8>;
|
||||
fn ancillary_payload(&self) -> Option<AncillaryData>;
|
||||
fn into_payload(self) -> (Vec<u8>, Option<AncillaryData>);
|
||||
fn decode(data: &[u8], ancillary_data: Option<AncillaryData>) -> Result<Self, MessageError>
|
||||
where
|
||||
Self: Sized;
|
||||
|
|
@ -126,16 +145,12 @@ impl Message for SetCrashReportPath {
|
|||
.encode()
|
||||
}
|
||||
|
||||
fn payload(&self) -> Vec<u8> {
|
||||
fn into_payload(self) -> (Vec<u8>, Option<AncillaryData>) {
|
||||
let mut payload = Vec::with_capacity(self.payload_size());
|
||||
let path = self.path.serialize();
|
||||
payload.extend(path.len().to_ne_bytes());
|
||||
payload.extend(self.path.serialize());
|
||||
payload
|
||||
}
|
||||
|
||||
fn ancillary_payload(&self) -> Option<AncillaryData> {
|
||||
None
|
||||
(payload, None)
|
||||
}
|
||||
|
||||
fn decode(
|
||||
|
|
@ -184,12 +199,8 @@ impl Message for TransferMinidump {
|
|||
.encode()
|
||||
}
|
||||
|
||||
fn payload(&self) -> Vec<u8> {
|
||||
self.pid.to_ne_bytes().to_vec()
|
||||
}
|
||||
|
||||
fn ancillary_payload(&self) -> Option<AncillaryData> {
|
||||
None
|
||||
fn into_payload(self) -> (Vec<u8>, Option<AncillaryData>) {
|
||||
(self.pid.to_ne_bytes().to_vec(), None)
|
||||
}
|
||||
|
||||
fn decode(
|
||||
|
|
@ -245,7 +256,7 @@ impl Message for TransferMinidumpReply {
|
|||
.encode()
|
||||
}
|
||||
|
||||
fn payload(&self) -> Vec<u8> {
|
||||
fn into_payload(self) -> (Vec<u8>, Option<AncillaryData>) {
|
||||
let path_bytes = self.path.serialize();
|
||||
let mut buffer = Vec::with_capacity(self.payload_size());
|
||||
buffer.extend(path_bytes.len().to_ne_bytes());
|
||||
|
|
@ -262,11 +273,7 @@ impl Message for TransferMinidumpReply {
|
|||
.as_ref()
|
||||
.map_or(Vec::new(), |error| Vec::from(error.as_bytes())),
|
||||
);
|
||||
buffer
|
||||
}
|
||||
|
||||
fn ancillary_payload(&self) -> Option<AncillaryData> {
|
||||
None
|
||||
(buffer, None)
|
||||
}
|
||||
|
||||
fn decode(
|
||||
|
|
@ -348,7 +355,7 @@ impl Message for WindowsErrorReportingMinidump {
|
|||
.encode()
|
||||
}
|
||||
|
||||
fn payload(&self) -> Vec<u8> {
|
||||
fn into_payload(self) -> (Vec<u8>, Option<AncillaryData>) {
|
||||
let mut buffer = Vec::<u8>::with_capacity(self.payload_size());
|
||||
buffer.extend(self.pid.to_ne_bytes());
|
||||
buffer.extend(self.tid.to_ne_bytes());
|
||||
|
|
@ -360,11 +367,7 @@ impl Message for WindowsErrorReportingMinidump {
|
|||
}
|
||||
let bytes: [u8; size_of::<CONTEXT>()] = unsafe { std::mem::transmute(self.context) };
|
||||
buffer.extend(bytes);
|
||||
buffer
|
||||
}
|
||||
|
||||
fn ancillary_payload(&self) -> Option<AncillaryData> {
|
||||
None
|
||||
(buffer, None)
|
||||
}
|
||||
|
||||
fn decode(
|
||||
|
|
@ -452,12 +455,8 @@ impl Message for WindowsErrorReportingMinidumpReply {
|
|||
.encode()
|
||||
}
|
||||
|
||||
fn payload(&self) -> Vec<u8> {
|
||||
Vec::<u8>::new()
|
||||
}
|
||||
|
||||
fn ancillary_payload(&self) -> Option<AncillaryData> {
|
||||
None
|
||||
fn into_payload(self) -> (Vec<u8>, Option<AncillaryData>) {
|
||||
(Vec::<u8>::new(), None)
|
||||
}
|
||||
|
||||
fn decode(
|
||||
|
|
@ -507,7 +506,7 @@ impl Message for RegisterAuxvInfo {
|
|||
.encode()
|
||||
}
|
||||
|
||||
fn payload(&self) -> Vec<u8> {
|
||||
fn into_payload(self) -> (Vec<u8>, Option<AncillaryData>) {
|
||||
let mut payload = Vec::with_capacity(self.payload_size());
|
||||
payload.extend(self.pid.to_ne_bytes());
|
||||
payload.extend(self.auxv_info.program_header_count.to_ne_bytes());
|
||||
|
|
@ -515,11 +514,7 @@ impl Message for RegisterAuxvInfo {
|
|||
payload.extend(self.auxv_info.linux_gate_address.to_ne_bytes());
|
||||
payload.extend(self.auxv_info.entry_address.to_ne_bytes());
|
||||
debug_assert!(self.payload_size() == payload.len());
|
||||
payload
|
||||
}
|
||||
|
||||
fn ancillary_payload(&self) -> Option<AncillaryData> {
|
||||
None
|
||||
(payload, None)
|
||||
}
|
||||
|
||||
fn decode(
|
||||
|
|
@ -598,15 +593,11 @@ impl Message for UnregisterAuxvInfo {
|
|||
.encode()
|
||||
}
|
||||
|
||||
fn payload(&self) -> Vec<u8> {
|
||||
fn into_payload(self) -> (Vec<u8>, Option<AncillaryData>) {
|
||||
let mut payload = Vec::with_capacity(self.payload_size());
|
||||
payload.extend(self.pid.to_ne_bytes());
|
||||
debug_assert!(self.payload_size() == payload.len());
|
||||
payload
|
||||
}
|
||||
|
||||
fn ancillary_payload(&self) -> Option<AncillaryData> {
|
||||
None
|
||||
(payload, None)
|
||||
}
|
||||
|
||||
fn decode(
|
||||
|
|
@ -627,7 +618,16 @@ impl Message for UnregisterAuxvInfo {
|
|||
|
||||
/* Message sent from the main process to the crash helper to register a new
|
||||
* child process which is about to be spawned. This message contains the IPC
|
||||
* endpoint which the crash helper will use to talk to the child. */
|
||||
* endpoint which the crash helper will use to talk to the child.
|
||||
*
|
||||
* Note that these processes should only contain an IPC endpoint and no actual
|
||||
* data, however they declare a 1-byte sized payload which is transferred but
|
||||
* ignored on the receiving size. This is a workaround to an issue with macOS
|
||||
* 10.15 implementation of Unix sockets which would sometimes fail to deliver
|
||||
* a message that would only contain control data and no buffer. See bug
|
||||
* 1989686 for more information. This dummy payload can be removed once bug
|
||||
* 2002791 is implemented.
|
||||
*/
|
||||
|
||||
pub struct RegisterChildProcess {
|
||||
pub ipc_endpoint: AncillaryData,
|
||||
|
|
@ -639,7 +639,7 @@ impl RegisterChildProcess {
|
|||
}
|
||||
|
||||
fn payload_size(&self) -> usize {
|
||||
0
|
||||
1
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -656,12 +656,8 @@ impl Message for RegisterChildProcess {
|
|||
.encode()
|
||||
}
|
||||
|
||||
fn payload(&self) -> Vec<u8> {
|
||||
Vec::<u8>::new()
|
||||
}
|
||||
|
||||
fn ancillary_payload(&self) -> Option<AncillaryData> {
|
||||
Some(self.ipc_endpoint)
|
||||
fn into_payload(self) -> (Vec<u8>, Option<AncillaryData>) {
|
||||
(vec![0], Some(self.ipc_endpoint))
|
||||
}
|
||||
|
||||
fn decode(
|
||||
|
|
@ -705,12 +701,8 @@ impl Message for ChildProcessRegistered {
|
|||
.encode()
|
||||
}
|
||||
|
||||
fn payload(&self) -> Vec<u8> {
|
||||
self.crash_helper_pid.to_ne_bytes().to_vec()
|
||||
}
|
||||
|
||||
fn ancillary_payload(&self) -> Option<AncillaryData> {
|
||||
None
|
||||
fn into_payload(self) -> (Vec<u8>, Option<AncillaryData>) {
|
||||
(self.crash_helper_pid.to_ne_bytes().to_vec(), None)
|
||||
}
|
||||
|
||||
fn decode(
|
||||
|
|
|
|||
|
|
@ -3,19 +3,19 @@
|
|||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
pub use windows::{server_addr, ProcessHandle};
|
||||
pub use windows::{server_addr, PlatformError, ProcessHandle};
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
pub(crate) mod windows;
|
||||
|
||||
#[cfg(any(target_os = "android", target_os = "linux"))]
|
||||
pub use linux::ProcessHandle;
|
||||
pub use linux::{PlatformError, ProcessHandle};
|
||||
|
||||
#[cfg(any(target_os = "android", target_os = "linux"))]
|
||||
pub(crate) mod linux;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub use macos::ProcessHandle;
|
||||
pub use macos::{PlatformError, ProcessHandle};
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub(crate) mod macos;
|
||||
|
|
|
|||
|
|
@ -3,35 +3,61 @@
|
|||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use nix::{
|
||||
errno::Errno,
|
||||
fcntl::{
|
||||
fcntl,
|
||||
FcntlArg::{F_GETFL, F_SETFD, F_SETFL},
|
||||
FdFlag, OFlag,
|
||||
},
|
||||
sys::socket::{socketpair, AddressFamily, SockFlag, SockType},
|
||||
Result,
|
||||
};
|
||||
use std::{
|
||||
os::fd::{BorrowedFd, OwnedFd},
|
||||
};
|
||||
use std::os::fd::{BorrowedFd, OwnedFd};
|
||||
use thiserror::Error;
|
||||
|
||||
pub type ProcessHandle = ();
|
||||
|
||||
pub(crate) fn unix_socketpair() -> Result<(OwnedFd, OwnedFd)> {
|
||||
#[derive(Error, Debug)]
|
||||
pub enum PlatformError {
|
||||
#[error("poll() call failed with error: {0}")]
|
||||
PollFailure(Errno),
|
||||
#[error("Could not set socket in non-blocking mode: {0}")]
|
||||
SocketNonBlockError(Errno),
|
||||
#[error("Could not flag socket as close-after-exec: {0}")]
|
||||
SocketCloexecError(Errno),
|
||||
#[error("Could not create a socket pair: {0}")]
|
||||
SocketpairFailure(#[from] Errno),
|
||||
#[error("sendmsg() call failed with error: {0}")]
|
||||
SendFailure(Errno),
|
||||
#[error("Sending {expected} bytes failed, only {sent} bytes sent")]
|
||||
SendTooShort { expected: usize, sent: usize },
|
||||
#[error("recvmsg() call failed with error: {0}")]
|
||||
ReceiveFailure(Errno),
|
||||
#[error("Missing SCM credentials")]
|
||||
ReceiveMissingCredentials,
|
||||
#[error("Receiving {expected} bytes failed, only {received} bytes received")]
|
||||
ReceiveTooShort { expected: usize, received: usize },
|
||||
}
|
||||
|
||||
pub(crate) fn unix_socketpair() -> Result<(OwnedFd, OwnedFd), PlatformError> {
|
||||
socketpair(
|
||||
AddressFamily::Unix,
|
||||
SockType::SeqPacket,
|
||||
None,
|
||||
SockFlag::empty(),
|
||||
)
|
||||
.map_err(PlatformError::SocketpairFailure)
|
||||
}
|
||||
|
||||
pub(crate) fn set_socket_default_flags(socket: BorrowedFd) -> Result<()> {
|
||||
pub(crate) fn set_socket_default_flags(socket: BorrowedFd) -> Result<(), PlatformError> {
|
||||
// All our sockets are in non-blocking mode.
|
||||
let flags = OFlag::from_bits_retain(fcntl(socket, F_GETFL)?);
|
||||
fcntl(socket, F_SETFL(flags.union(OFlag::O_NONBLOCK))).map(|_res| ())
|
||||
fcntl(socket, F_SETFL(flags.union(OFlag::O_NONBLOCK)))
|
||||
.map(|_res| ())
|
||||
.map_err(PlatformError::SocketNonBlockError)
|
||||
}
|
||||
|
||||
pub(crate) fn set_socket_cloexec(socket: BorrowedFd) -> Result<()> {
|
||||
fcntl(socket, F_SETFD(FdFlag::FD_CLOEXEC)).map(|_res| ())
|
||||
pub(crate) fn set_socket_cloexec(socket: BorrowedFd) -> Result<(), PlatformError> {
|
||||
fcntl(socket, F_SETFD(FdFlag::FD_CLOEXEC))
|
||||
.map(|_res| ())
|
||||
.map_err(PlatformError::SocketCloexecError)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,25 +11,48 @@ use nix::{
|
|||
},
|
||||
libc::{setsockopt, SOL_SOCKET, SO_NOSIGPIPE},
|
||||
sys::socket::{socketpair, AddressFamily, SockFlag, SockType},
|
||||
Result,
|
||||
};
|
||||
use std::{
|
||||
mem::size_of,
|
||||
os::fd::{AsRawFd, BorrowedFd, OwnedFd},
|
||||
};
|
||||
use thiserror::Error;
|
||||
|
||||
pub type ProcessHandle = ();
|
||||
|
||||
pub(crate) fn unix_socketpair() -> Result<(OwnedFd, OwnedFd)> {
|
||||
#[derive(Error, Debug)]
|
||||
pub enum PlatformError {
|
||||
#[error("poll() call failed with error: {0}")]
|
||||
PollFailure(Errno),
|
||||
#[error("Could not set socket in non-blocking mode: {0}")]
|
||||
SocketNonBlockError(Errno),
|
||||
#[error("Could not flag socket as close-after-exec: {0}")]
|
||||
SocketCloexecError(Errno),
|
||||
#[error("Could not create a socket pair: {0}")]
|
||||
SocketpairFailure(#[from] Errno),
|
||||
#[error("sendmsg() call failed with error: {0}")]
|
||||
SendFailure(Errno),
|
||||
#[error("Sending {expected} bytes failed, only {sent} bytes sent")]
|
||||
SendTooShort { expected: usize, sent: usize },
|
||||
#[error("recvmsg() call failed with error: {0}")]
|
||||
ReceiveFailure(Errno),
|
||||
#[error("Missing SCM credentials")]
|
||||
ReceiveMissingCredentials,
|
||||
#[error("Receiving {expected} bytes failed, only {received} bytes received")]
|
||||
ReceiveTooShort { expected: usize, received: usize },
|
||||
}
|
||||
|
||||
pub(crate) fn unix_socketpair() -> Result<(OwnedFd, OwnedFd), PlatformError> {
|
||||
socketpair(
|
||||
AddressFamily::Unix,
|
||||
SockType::Stream,
|
||||
None,
|
||||
SockFlag::empty(),
|
||||
)
|
||||
.map_err(PlatformError::SocketpairFailure)
|
||||
}
|
||||
|
||||
pub(crate) fn set_socket_default_flags(socket: BorrowedFd) -> Result<()> {
|
||||
pub(crate) fn set_socket_default_flags(socket: BorrowedFd) -> Result<(), PlatformError> {
|
||||
// All our sockets are in non-blocking mode.
|
||||
let flags = OFlag::from_bits_retain(fcntl(socket, F_GETFL)?);
|
||||
fcntl(socket, F_SETFL(flags.union(OFlag::O_NONBLOCK)))?;
|
||||
|
|
@ -48,12 +71,14 @@ pub(crate) fn set_socket_default_flags(socket: BorrowedFd) -> Result<()> {
|
|||
};
|
||||
|
||||
if res < 0 {
|
||||
return Err(Errno::last());
|
||||
return Err(PlatformError::SocketNonBlockError(Errno::last()));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn set_socket_cloexec(socket: BorrowedFd) -> Result<()> {
|
||||
fcntl(socket, F_SETFD(FdFlag::FD_CLOEXEC)).map(|_res| ())
|
||||
pub(crate) fn set_socket_cloexec(socket: BorrowedFd) -> Result<(), PlatformError> {
|
||||
fcntl(socket, F_SETFD(FdFlag::FD_CLOEXEC))
|
||||
.map(|_res| ())
|
||||
.map_err(PlatformError::SocketCloexecError)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,19 +2,20 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use crate::{
|
||||
errors::{IPCError, MessageError},
|
||||
Pid, IO_TIMEOUT,
|
||||
};
|
||||
use crate::{Pid, IO_TIMEOUT};
|
||||
use std::{
|
||||
ffi::CString,
|
||||
mem::zeroed,
|
||||
os::windows::io::{AsRawHandle, FromRawHandle, OwnedHandle, RawHandle},
|
||||
mem::{zeroed, MaybeUninit},
|
||||
os::windows::io::{
|
||||
AsHandle, AsRawHandle, BorrowedHandle, FromRawHandle, OwnedHandle, RawHandle,
|
||||
},
|
||||
ptr::{null, null_mut},
|
||||
rc::Rc,
|
||||
};
|
||||
use thiserror::Error;
|
||||
use windows_sys::Win32::{
|
||||
Foundation::{
|
||||
GetLastError, ERROR_IO_INCOMPLETE, ERROR_IO_PENDING, ERROR_NOT_FOUND, ERROR_PIPE_CONNECTED,
|
||||
GetLastError, ERROR_BROKEN_PIPE, ERROR_IO_PENDING, ERROR_NOT_FOUND, ERROR_PIPE_CONNECTED,
|
||||
FALSE, HANDLE, WAIT_TIMEOUT, WIN32_ERROR,
|
||||
},
|
||||
Storage::FileSystem::{ReadFile, WriteFile},
|
||||
|
|
@ -27,6 +28,36 @@ use windows_sys::Win32::{
|
|||
|
||||
pub type ProcessHandle = OwnedHandle;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum PlatformError {
|
||||
#[error("Could not accept incoming connection: {0}")]
|
||||
AcceptFailed(WIN32_ERROR),
|
||||
#[error("Broken pipe")]
|
||||
BrokenPipe,
|
||||
#[error("Failed to duplicate clone handle")]
|
||||
CloneHandleFailed(#[source] std::io::Error),
|
||||
#[error("Could not create event: {0}")]
|
||||
CreateEventFailed(WIN32_ERROR),
|
||||
#[error("Could not create a pipe: {0}")]
|
||||
CreatePipeFailure(WIN32_ERROR),
|
||||
#[error("I/O error: {0}")]
|
||||
IOError(WIN32_ERROR),
|
||||
#[error("No process handle specified")]
|
||||
MissingProcessHandle,
|
||||
#[error("Could not listen for incoming connections: {0}")]
|
||||
ListenFailed(WIN32_ERROR),
|
||||
#[error("Receiving {expected} bytes failed, only {received} bytes received")]
|
||||
ReceiveTooShort { expected: usize, received: usize },
|
||||
#[error("Could not reset event: {0}")]
|
||||
ResetEventFailed(WIN32_ERROR),
|
||||
#[error("Sending {expected} bytes failed, only {sent} bytes sent")]
|
||||
SendTooShort { expected: usize, sent: usize },
|
||||
#[error("Could not set event: {0}")]
|
||||
SetEventFailed(WIN32_ERROR),
|
||||
#[error("Value too large")]
|
||||
ValueTooLarge,
|
||||
}
|
||||
|
||||
pub(crate) fn get_last_error() -> WIN32_ERROR {
|
||||
// SAFETY: This is always safe to call
|
||||
unsafe { GetLastError() }
|
||||
|
|
@ -37,7 +68,7 @@ pub fn server_addr(pid: Pid) -> CString {
|
|||
CString::new(format!("\\\\.\\pipe\\gecko-crash-helper-pipe.{pid:}")).unwrap()
|
||||
}
|
||||
|
||||
pub(crate) fn create_manual_reset_event() -> Result<OwnedHandle, IPCError> {
|
||||
pub(crate) fn create_manual_reset_event() -> Result<OwnedHandle, PlatformError> {
|
||||
// SAFETY: We pass null pointers for all the pointer arguments.
|
||||
let raw_handle = unsafe {
|
||||
CreateEventA(
|
||||
|
|
@ -49,27 +80,36 @@ pub(crate) fn create_manual_reset_event() -> Result<OwnedHandle, IPCError> {
|
|||
} as RawHandle;
|
||||
|
||||
if raw_handle.is_null() {
|
||||
return Err(IPCError::System(get_last_error()));
|
||||
return Err(PlatformError::CreateEventFailed(get_last_error()));
|
||||
}
|
||||
|
||||
// SAFETY: We just verified that `raw_handle` is valid.
|
||||
Ok(unsafe { OwnedHandle::from_raw_handle(raw_handle) })
|
||||
}
|
||||
|
||||
fn set_event(handle: HANDLE) -> Result<(), IPCError> {
|
||||
fn set_event(handle: BorrowedHandle) -> Result<(), PlatformError> {
|
||||
// SAFETY: This is always safe, even when passing an invalid handle.
|
||||
if unsafe { SetEvent(handle) } == FALSE {
|
||||
Err(IPCError::System(get_last_error()))
|
||||
if unsafe { SetEvent(handle.as_raw_handle() as HANDLE) } == FALSE {
|
||||
Err(PlatformError::SetEventFailed(get_last_error()))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn cancel_overlapped_io(handle: HANDLE, overlapped: &mut OVERLAPPED) -> bool {
|
||||
fn reset_event(handle: BorrowedHandle) -> Result<(), PlatformError> {
|
||||
// SAFETY: This is always safe, even when passing an invalid handle.
|
||||
if unsafe { ResetEvent(handle.as_raw_handle() as HANDLE) } == FALSE {
|
||||
Err(PlatformError::ResetEventFailed(get_last_error()))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn cancel_overlapped_io(handle: BorrowedHandle, overlapped: &OVERLAPPED) -> bool {
|
||||
// SAFETY: the pointer to the overlapped structure is always valid as the
|
||||
// structure is passed by reference. The handle should be valid but will
|
||||
// be handled properly in case it isn't.
|
||||
let res = unsafe { CancelIoEx(handle, overlapped) };
|
||||
let res = unsafe { CancelIoEx(handle.as_raw_handle() as HANDLE, overlapped) };
|
||||
if res == FALSE {
|
||||
if get_last_error() == ERROR_NOT_FOUND {
|
||||
// There was no pending operation
|
||||
|
|
@ -79,14 +119,19 @@ fn cancel_overlapped_io(handle: HANDLE, overlapped: &mut OVERLAPPED) -> bool {
|
|||
return false;
|
||||
}
|
||||
|
||||
if overlapped.hEvent == 0 {
|
||||
// No associated event, don't wait
|
||||
return true;
|
||||
}
|
||||
|
||||
// Just wait for the operation to finish, we don't care about the result
|
||||
let mut number_of_bytes_transferred: u32 = 0;
|
||||
let mut number_of_bytes_transferred = MaybeUninit::<u32>::uninit();
|
||||
// SAFETY: Same as above
|
||||
let res = unsafe {
|
||||
GetOverlappedResultEx(
|
||||
handle,
|
||||
handle.as_raw_handle() as HANDLE,
|
||||
overlapped,
|
||||
&mut number_of_bytes_transferred,
|
||||
number_of_bytes_transferred.as_mut_ptr(),
|
||||
INFINITE,
|
||||
/* bAlertable */ FALSE,
|
||||
)
|
||||
|
|
@ -96,7 +141,7 @@ fn cancel_overlapped_io(handle: HANDLE, overlapped: &mut OVERLAPPED) -> bool {
|
|||
}
|
||||
|
||||
pub(crate) struct OverlappedOperation {
|
||||
handle: OwnedHandle,
|
||||
handle: Rc<OwnedHandle>,
|
||||
overlapped: Option<Box<OVERLAPPED>>,
|
||||
buffer: Option<Vec<u8>>,
|
||||
}
|
||||
|
|
@ -107,11 +152,9 @@ enum OverlappedOperationType {
|
|||
}
|
||||
|
||||
impl OverlappedOperation {
|
||||
pub(crate) fn listen(
|
||||
handle: OwnedHandle,
|
||||
event: HANDLE,
|
||||
) -> Result<OverlappedOperation, IPCError> {
|
||||
let mut overlapped = Self::overlapped_with_event(event)?;
|
||||
// Asynchronously listen for an incoming connection
|
||||
pub(crate) fn listen(handle: &Rc<OwnedHandle>) -> Result<OverlappedOperation, PlatformError> {
|
||||
let mut overlapped = Self::overlapped();
|
||||
|
||||
// SAFETY: We guarantee that the handle and OVERLAPPED object are both
|
||||
// valid and remain so while used by this function.
|
||||
|
|
@ -122,47 +165,42 @@ impl OverlappedOperation {
|
|||
if res != FALSE {
|
||||
// According to Microsoft's documentation this should never happen,
|
||||
// we check out of an abundance of caution.
|
||||
return Err(IPCError::System(error));
|
||||
return Err(PlatformError::ListenFailed(error));
|
||||
}
|
||||
|
||||
if error == ERROR_PIPE_CONNECTED {
|
||||
// The operation completed synchronously, set the event so that
|
||||
// waiting on it will return immediately.
|
||||
set_event(event)?;
|
||||
} else if error != ERROR_IO_PENDING {
|
||||
return Err(IPCError::System(error));
|
||||
}
|
||||
match error {
|
||||
ERROR_PIPE_CONNECTED | ERROR_IO_PENDING => {
|
||||
// The operations succeeded, we'll get a completion event
|
||||
}
|
||||
error => return Err(PlatformError::ListenFailed(error)),
|
||||
};
|
||||
|
||||
Ok(OverlappedOperation {
|
||||
handle,
|
||||
handle: handle.clone(),
|
||||
overlapped: Some(overlapped),
|
||||
buffer: None,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn accept(mut self, handle: HANDLE) -> Result<(), IPCError> {
|
||||
// Synchronously accept an incoming connection, does not wait and fails if
|
||||
// no incoming connection is present.
|
||||
pub(crate) fn accept(mut self) -> Result<(), PlatformError> {
|
||||
let overlapped = self.overlapped.take().unwrap();
|
||||
let mut _number_of_bytes_transferred: u32 = 0;
|
||||
let mut number_of_bytes_transferred = MaybeUninit::<u32>::uninit();
|
||||
// SAFETY: The pointer to the OVERLAPPED structure is under our
|
||||
// control and thus guaranteed to be valid.
|
||||
let res = unsafe {
|
||||
GetOverlappedResultEx(
|
||||
handle,
|
||||
self.handle.as_raw_handle() as HANDLE,
|
||||
overlapped.as_ref(),
|
||||
&mut _number_of_bytes_transferred,
|
||||
number_of_bytes_transferred.as_mut_ptr(),
|
||||
0,
|
||||
/* bAlertable */ FALSE,
|
||||
)
|
||||
};
|
||||
|
||||
if res == FALSE {
|
||||
let error = get_last_error();
|
||||
if error == ERROR_IO_INCOMPLETE {
|
||||
// The I/O operation did not complete yet
|
||||
self.cancel_or_leak(overlapped, None);
|
||||
}
|
||||
|
||||
return Err(IPCError::System(error));
|
||||
return Err(PlatformError::AcceptFailed(get_last_error()));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
@ -171,35 +209,49 @@ impl OverlappedOperation {
|
|||
fn await_io(
|
||||
mut self,
|
||||
optype: OverlappedOperationType,
|
||||
wait: bool,
|
||||
) -> Result<Option<Vec<u8>>, IPCError> {
|
||||
) -> Result<Option<Vec<u8>>, PlatformError> {
|
||||
let overlapped = self.overlapped.take().unwrap();
|
||||
let buffer = self.buffer.take().unwrap();
|
||||
let mut number_of_bytes_transferred: u32 = 0;
|
||||
let mut number_of_bytes_transferred = MaybeUninit::<u32>::uninit();
|
||||
// SAFETY: All the pointers passed to this call are under our control
|
||||
// and thus guaranteed to be valid.
|
||||
let res = unsafe {
|
||||
GetOverlappedResultEx(
|
||||
self.handle.as_raw_handle() as HANDLE,
|
||||
overlapped.as_ref(),
|
||||
&mut number_of_bytes_transferred,
|
||||
if wait { IO_TIMEOUT as u32 } else { 0 },
|
||||
number_of_bytes_transferred.as_mut_ptr(),
|
||||
IO_TIMEOUT as u32,
|
||||
/* bAlertable */ FALSE,
|
||||
)
|
||||
};
|
||||
|
||||
if res == FALSE {
|
||||
let error = get_last_error();
|
||||
if (wait && (error == WAIT_TIMEOUT)) || (!wait && (error == ERROR_IO_INCOMPLETE)) {
|
||||
if error == WAIT_TIMEOUT {
|
||||
// The I/O operation did not complete yet
|
||||
self.cancel_or_leak(overlapped, Some(buffer));
|
||||
} else if error == ERROR_BROKEN_PIPE {
|
||||
return Err(PlatformError::BrokenPipe);
|
||||
}
|
||||
|
||||
return Err(IPCError::System(error));
|
||||
return Err(PlatformError::IOError(error));
|
||||
}
|
||||
|
||||
if (number_of_bytes_transferred as usize) != buffer.len() {
|
||||
return Err(IPCError::BadMessage(MessageError::InvalidData));
|
||||
// SAFETY: We've verified that `number_of_bytes_transferred` has been
|
||||
// populated by the `GetOverlappedResultEx()` call.
|
||||
let number_of_bytes_transferred = unsafe { number_of_bytes_transferred.assume_init() };
|
||||
|
||||
if number_of_bytes_transferred as usize != buffer.len() {
|
||||
return Err(match optype {
|
||||
OverlappedOperationType::Read => PlatformError::ReceiveTooShort {
|
||||
expected: buffer.len(),
|
||||
received: number_of_bytes_transferred as usize,
|
||||
},
|
||||
OverlappedOperationType::Write => PlatformError::SendTooShort {
|
||||
expected: buffer.len(),
|
||||
sent: number_of_bytes_transferred as usize,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
Ok(match optype {
|
||||
|
|
@ -208,14 +260,20 @@ impl OverlappedOperation {
|
|||
})
|
||||
}
|
||||
|
||||
pub(crate) fn sched_recv(
|
||||
handle: OwnedHandle,
|
||||
event: HANDLE,
|
||||
fn sched_recv_internal(
|
||||
handle: &Rc<OwnedHandle>,
|
||||
event: Option<BorrowedHandle>,
|
||||
expected_size: usize,
|
||||
) -> Result<OverlappedOperation, IPCError> {
|
||||
let mut overlapped = Self::overlapped_with_event(event)?;
|
||||
) -> Result<OverlappedOperation, PlatformError> {
|
||||
let mut overlapped = if let Some(event) = event {
|
||||
OverlappedOperation::overlapped_with_event(event)?
|
||||
} else {
|
||||
OverlappedOperation::overlapped()
|
||||
};
|
||||
let mut buffer = vec![0u8; expected_size];
|
||||
let number_of_bytes_to_read: u32 = expected_size.try_into()?;
|
||||
let number_of_bytes_to_read: u32 = expected_size
|
||||
.try_into()
|
||||
.map_err(|_e| PlatformError::ValueTooLarge)?;
|
||||
// SAFETY: We control all the pointers going into this call, guarantee
|
||||
// that they're valid and that they will be alive for the entire
|
||||
// duration of the asynchronous operation.
|
||||
|
|
@ -231,31 +289,56 @@ impl OverlappedOperation {
|
|||
|
||||
let error = get_last_error();
|
||||
if res != FALSE {
|
||||
// The operation completed synchronously, set the event so that
|
||||
// waiting on it will return immediately.
|
||||
set_event(event)?;
|
||||
if let Some(event) = event {
|
||||
// The operation completed synchronously, if we have an event
|
||||
// set it so that waiting on it will return immediately.
|
||||
set_event(event)?;
|
||||
}
|
||||
} else if error == ERROR_BROKEN_PIPE {
|
||||
return Err(PlatformError::BrokenPipe);
|
||||
} else if error != ERROR_IO_PENDING {
|
||||
return Err(IPCError::System(error));
|
||||
return Err(PlatformError::IOError(error));
|
||||
}
|
||||
|
||||
Ok(OverlappedOperation {
|
||||
handle,
|
||||
handle: handle.clone(),
|
||||
overlapped: Some(overlapped),
|
||||
buffer: Some(buffer),
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn collect_recv(self, wait: bool) -> Result<Vec<u8>, IPCError> {
|
||||
Ok(self.await_io(OverlappedOperationType::Read, wait)?.unwrap())
|
||||
pub(crate) fn recv(
|
||||
handle: &Rc<OwnedHandle>,
|
||||
event: BorrowedHandle<'_>,
|
||||
expected_size: usize,
|
||||
) -> Result<Vec<u8>, PlatformError> {
|
||||
let overlapped = Self::sched_recv_internal(handle, Some(event), expected_size)?;
|
||||
overlapped
|
||||
.await_io(OverlappedOperationType::Read)
|
||||
.map(|buffer| buffer.unwrap())
|
||||
}
|
||||
|
||||
pub(crate) fn sched_send(
|
||||
handle: OwnedHandle,
|
||||
event: HANDLE,
|
||||
pub(crate) fn sched_recv(
|
||||
handle: &Rc<OwnedHandle>,
|
||||
expected_size: usize,
|
||||
) -> Result<OverlappedOperation, PlatformError> {
|
||||
Self::sched_recv_internal(handle, None, expected_size)
|
||||
}
|
||||
|
||||
pub(crate) fn collect_recv(mut self) -> Vec<u8> {
|
||||
self.buffer.take().expect("Missing receive buffer")
|
||||
}
|
||||
|
||||
pub(crate) fn send(
|
||||
handle: &Rc<OwnedHandle>,
|
||||
event: BorrowedHandle<'_>,
|
||||
mut buffer: Vec<u8>,
|
||||
) -> Result<OverlappedOperation, IPCError> {
|
||||
) -> Result<(), PlatformError> {
|
||||
let mut overlapped = Self::overlapped_with_event(event)?;
|
||||
let number_of_bytes_to_write: u32 = buffer.len().try_into()?;
|
||||
let number_of_bytes_to_write: u32 = buffer
|
||||
.len()
|
||||
.try_into()
|
||||
.map_err(|_e| PlatformError::ValueTooLarge)?;
|
||||
// SAFETY: We control all the pointers going into this call, guarantee
|
||||
// that they're valid and that they will be alive for the entire
|
||||
// duration of the asynchronous operation.
|
||||
|
|
@ -274,36 +357,64 @@ impl OverlappedOperation {
|
|||
// The operation completed synchronously, set the event so that
|
||||
// waiting on it will return immediately.
|
||||
set_event(event)?;
|
||||
} else if error == ERROR_BROKEN_PIPE {
|
||||
return Err(PlatformError::BrokenPipe);
|
||||
} else if error != ERROR_IO_PENDING {
|
||||
return Err(IPCError::System(error));
|
||||
return Err(PlatformError::IOError(error));
|
||||
}
|
||||
|
||||
Ok(OverlappedOperation {
|
||||
handle,
|
||||
let overlapped = OverlappedOperation {
|
||||
handle: handle.clone(),
|
||||
overlapped: Some(overlapped),
|
||||
buffer: Some(buffer),
|
||||
})
|
||||
};
|
||||
|
||||
overlapped
|
||||
.await_io(OverlappedOperationType::Write)
|
||||
.map(|buffer| {
|
||||
debug_assert!(buffer.is_none());
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn complete_send(self, wait: bool) -> Result<(), IPCError> {
|
||||
self.await_io(OverlappedOperationType::Write, wait)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn overlapped_with_event(event: HANDLE) -> Result<Box<OVERLAPPED>, IPCError> {
|
||||
// SAFETY: This is always safe, even when passing an invalid handle.
|
||||
if unsafe { ResetEvent(event) } == FALSE {
|
||||
return Err(IPCError::System(get_last_error()));
|
||||
}
|
||||
fn overlapped_with_event(event: BorrowedHandle<'_>) -> Result<Box<OVERLAPPED>, PlatformError> {
|
||||
reset_event(event)?;
|
||||
|
||||
// We set the last bit of the `hEvent` field to prevent this overlapped
|
||||
// operation from generating completion events. The event handle will
|
||||
// be notified instead when it completes.
|
||||
Ok(Box::new(OVERLAPPED {
|
||||
hEvent: event,
|
||||
hEvent: event.as_raw_handle() as HANDLE | 1,
|
||||
..unsafe { zeroed() }
|
||||
}))
|
||||
}
|
||||
|
||||
fn overlapped() -> Box<OVERLAPPED> {
|
||||
Box::new(unsafe { zeroed() })
|
||||
}
|
||||
|
||||
/// Cancel the pending operation but leave the buffers intact. It's the
|
||||
/// caller's responsibility to wait for the operation to complete and free
|
||||
/// the buffers.
|
||||
pub(crate) fn cancel(&self) -> bool {
|
||||
if let Some(overlapped) = self.overlapped.as_deref() {
|
||||
return cancel_overlapped_io(self.handle.as_handle(), overlapped);
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
/// Leak the buffers involved in the operation.
|
||||
pub(crate) fn leak(&mut self) {
|
||||
if let Some(overlapped) = self.overlapped.take() {
|
||||
Box::leak(overlapped);
|
||||
if let Some(buffer) = self.buffer.take() {
|
||||
buffer.leak();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn cancel_or_leak(&self, mut overlapped: Box<OVERLAPPED>, buffer: Option<Vec<u8>>) {
|
||||
if !cancel_overlapped_io(self.handle.as_raw_handle() as HANDLE, overlapped.as_mut()) {
|
||||
if !cancel_overlapped_io(self.handle.as_handle(), overlapped.as_mut()) {
|
||||
// If we cannot cancel the operation we must leak the
|
||||
// associated buffers so that they're available in case it
|
||||
// ever completes.
|
||||
|
|
@ -320,6 +431,10 @@ impl Drop for OverlappedOperation {
|
|||
let overlapped = self.overlapped.take();
|
||||
let buffer = self.buffer.take();
|
||||
if let Some(overlapped) = overlapped {
|
||||
if overlapped.hEvent == 0 {
|
||||
return; // This operation should have already been cancelled.
|
||||
}
|
||||
|
||||
self.cancel_or_leak(overlapped, buffer);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -78,7 +78,6 @@ enum MinidumpOrigin {
|
|||
|
||||
pub(crate) enum MessageResult {
|
||||
None,
|
||||
Reply(Box<dyn Message>),
|
||||
Connection(IPCConnector),
|
||||
}
|
||||
|
||||
|
|
@ -111,6 +110,7 @@ impl CrashGenerator {
|
|||
// reply that will be sent back to the parent.
|
||||
pub(crate) fn parent_message(
|
||||
&mut self,
|
||||
client: &IPCConnector,
|
||||
kind: messages::Kind,
|
||||
data: &[u8],
|
||||
ancillary_data: Option<AncillaryData>,
|
||||
|
|
@ -123,9 +123,8 @@ impl CrashGenerator {
|
|||
}
|
||||
messages::Kind::TransferMinidump => {
|
||||
let message = messages::TransferMinidump::decode(data, ancillary_data)?;
|
||||
Ok(MessageResult::Reply(Box::new(
|
||||
self.transfer_minidump(message.pid),
|
||||
)))
|
||||
client.send_message(self.transfer_minidump(message.pid))?;
|
||||
Ok(MessageResult::None)
|
||||
}
|
||||
messages::Kind::GenerateMinidump => {
|
||||
todo!("Implement all messages");
|
||||
|
|
@ -150,7 +149,7 @@ impl CrashGenerator {
|
|||
let message = messages::RegisterChildProcess::decode(data, ancillary_data)?;
|
||||
let connector = IPCConnector::from_ancillary(message.ipc_endpoint)?;
|
||||
connector
|
||||
.send_message(&messages::ChildProcessRegistered::new(process::id() as Pid))?;
|
||||
.send_message(messages::ChildProcessRegistered::new(process::id() as Pid))?;
|
||||
Ok(MessageResult::Connection(connector))
|
||||
}
|
||||
kind => {
|
||||
|
|
@ -174,6 +173,7 @@ impl CrashGenerator {
|
|||
// reply that will be sent back.
|
||||
pub(crate) fn external_message(
|
||||
&mut self,
|
||||
#[allow(unused_variables)] runtime: &IPCConnector,
|
||||
kind: messages::Kind,
|
||||
#[allow(unused_variables)] data: &[u8],
|
||||
#[allow(unused_variables)] ancillary_data: Option<AncillaryData>,
|
||||
|
|
@ -184,9 +184,8 @@ impl CrashGenerator {
|
|||
let message =
|
||||
messages::WindowsErrorReportingMinidump::decode(data, ancillary_data)?;
|
||||
let _ = self.generate_wer_minidump(message);
|
||||
Ok(MessageResult::Reply(Box::new(
|
||||
messages::WindowsErrorReportingMinidumpReply::new(),
|
||||
)))
|
||||
runtime.send_message(messages::WindowsErrorReportingMinidumpReply::new())?;
|
||||
Ok(MessageResult::None)
|
||||
}
|
||||
kind => {
|
||||
bail!("Unexpected message {kind:?} from external process");
|
||||
|
|
|
|||
|
|
@ -3,15 +3,13 @@
|
|||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use anyhow::Result;
|
||||
use crash_helper_common::{errors::IPCError, messages, IPCConnector, IPCEvent, IPCListener};
|
||||
use crash_helper_common::{
|
||||
messages::Header, AncillaryData, IPCConnector, IPCConnectorKey, IPCEvent, IPCListener, IPCQueue,
|
||||
};
|
||||
use std::{collections::HashMap, rc::Rc};
|
||||
|
||||
use crate::crash_generation::{CrashGenerator, MessageResult};
|
||||
|
||||
#[cfg(any(target_os = "android", target_os = "linux", target_os = "macos"))]
|
||||
mod unix;
|
||||
#[cfg(target_os = "windows")]
|
||||
mod windows;
|
||||
|
||||
#[derive(PartialEq)]
|
||||
pub enum IPCServerState {
|
||||
Running,
|
||||
|
|
@ -21,61 +19,73 @@ pub enum IPCServerState {
|
|||
#[derive(PartialEq)]
|
||||
enum IPCEndpoint {
|
||||
Parent, // A connection to the parent process
|
||||
#[allow(dead_code)]
|
||||
|
||||
Child, // A connection to the child process
|
||||
#[allow(dead_code)]
|
||||
External, // A connection to an external process
|
||||
}
|
||||
|
||||
struct IPCConnection {
|
||||
connector: IPCConnector,
|
||||
connector: Rc<IPCConnector>,
|
||||
endpoint: IPCEndpoint,
|
||||
}
|
||||
|
||||
pub(crate) struct IPCServer {
|
||||
#[cfg_attr(unix, allow(dead_code))]
|
||||
listener: IPCListener,
|
||||
connections: Vec<IPCConnection>,
|
||||
/// Platform-specific mechanism to wait for events. This will contain
|
||||
/// references to the connectors so needs to be the first element in
|
||||
/// the structure so that it's dropped first.
|
||||
queue: IPCQueue,
|
||||
connections: HashMap<IPCConnectorKey, IPCConnection>,
|
||||
}
|
||||
|
||||
impl IPCServer {
|
||||
pub(crate) fn new(listener: IPCListener, connector: IPCConnector) -> IPCServer {
|
||||
IPCServer {
|
||||
listener,
|
||||
connections: vec![IPCConnection {
|
||||
pub(crate) fn new(listener: IPCListener, connector: IPCConnector) -> Result<IPCServer> {
|
||||
let connector = Rc::new(connector);
|
||||
let mut queue = IPCQueue::new(listener)?;
|
||||
queue.add_connector(&connector)?;
|
||||
|
||||
let mut connections = HashMap::with_capacity(10);
|
||||
connections.insert(
|
||||
connector.key(),
|
||||
IPCConnection {
|
||||
connector,
|
||||
endpoint: IPCEndpoint::Parent,
|
||||
}],
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
Ok(IPCServer { queue, connections })
|
||||
}
|
||||
|
||||
pub(crate) fn run(
|
||||
&mut self,
|
||||
generator: &mut CrashGenerator,
|
||||
) -> Result<IPCServerState, IPCError> {
|
||||
let events = self.wait_for_events()?;
|
||||
pub(crate) fn run(&mut self, generator: &mut CrashGenerator) -> Result<IPCServerState> {
|
||||
let events = self.queue.wait_for_events()?;
|
||||
|
||||
// We reverse the order of events, so that we start processing them
|
||||
// from the highest indexes toward the lowest. If we did the opposite
|
||||
// removed connections would invalidate the successive indexes.
|
||||
for event in events.into_iter().rev() {
|
||||
for event in events.into_iter() {
|
||||
match event {
|
||||
IPCEvent::Connect(connector) => {
|
||||
self.connections.push(IPCConnection {
|
||||
connector,
|
||||
endpoint: IPCEndpoint::External,
|
||||
});
|
||||
self.connections.insert(
|
||||
connector.key(),
|
||||
IPCConnection {
|
||||
connector,
|
||||
endpoint: IPCEndpoint::External,
|
||||
},
|
||||
);
|
||||
}
|
||||
IPCEvent::Header(index, header) => {
|
||||
let res = self.handle_message(index, &header, generator);
|
||||
if let Err(error) = res {
|
||||
IPCEvent::Message(key, header, payload, ancillary_data) => {
|
||||
if let Err(error) =
|
||||
self.handle_message(key, &header, payload, ancillary_data, generator)
|
||||
{
|
||||
log::error!(
|
||||
"Error {error} while handling a message of {:?} kind",
|
||||
"Error {error} when handling a message of kind {:?}",
|
||||
header.kind
|
||||
);
|
||||
}
|
||||
}
|
||||
IPCEvent::Disconnect(index) => {
|
||||
let connection = self.connections.remove(index);
|
||||
IPCEvent::Disconnect(key) => {
|
||||
let connection = self
|
||||
.connections
|
||||
.remove(&key)
|
||||
.expect("Disconnection event but no corresponding connection");
|
||||
|
||||
if connection.endpoint == IPCEndpoint::Parent {
|
||||
// The main process disconnected, leave
|
||||
return Ok(IPCServerState::ClientDisconnected);
|
||||
|
|
@ -89,34 +99,41 @@ impl IPCServer {
|
|||
|
||||
fn handle_message(
|
||||
&mut self,
|
||||
index: usize,
|
||||
header: &messages::Header,
|
||||
key: IPCConnectorKey,
|
||||
header: &Header,
|
||||
data: Vec<u8>,
|
||||
ancillary_data: Option<AncillaryData>,
|
||||
generator: &mut CrashGenerator,
|
||||
) -> Result<()> {
|
||||
let connection = self
|
||||
.connections
|
||||
.get_mut(index)
|
||||
.expect("Invalid connector index");
|
||||
let connector = &mut connection.connector;
|
||||
let (data, ancillary_data) = connector.recv(header.size)?;
|
||||
.get(&key)
|
||||
.expect("Event received on non-existing connection");
|
||||
let connector = &connection.connector;
|
||||
|
||||
let reply = match connection.endpoint {
|
||||
IPCEndpoint::Parent => generator.parent_message(header.kind, &data, ancillary_data),
|
||||
IPCEndpoint::Child => generator.child_message(header.kind, &data, ancillary_data),
|
||||
IPCEndpoint::External => generator.external_message(header.kind, &data, ancillary_data),
|
||||
}?;
|
||||
|
||||
match reply {
|
||||
MessageResult::Reply(reply) => connector.send_message(reply.as_ref())?,
|
||||
MessageResult::Connection(connector) => {
|
||||
self.connections.push(IPCConnection {
|
||||
connector,
|
||||
endpoint: IPCEndpoint::Child,
|
||||
});
|
||||
match connection.endpoint {
|
||||
IPCEndpoint::Parent => {
|
||||
let res =
|
||||
generator.parent_message(connector, header.kind, &data, ancillary_data)?;
|
||||
if let MessageResult::Connection(connector) = res {
|
||||
let connector = Rc::new(connector);
|
||||
self.queue.add_connector(&connector)?;
|
||||
self.connections.insert(
|
||||
connector.key(),
|
||||
IPCConnection {
|
||||
connector,
|
||||
endpoint: IPCEndpoint::Child,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
MessageResult::None => {}
|
||||
}
|
||||
IPCEndpoint::Child => {
|
||||
generator.child_message(header.kind, &data, ancillary_data)?;
|
||||
}
|
||||
IPCEndpoint::External => {
|
||||
generator.external_message(connector, header.kind, &data, ancillary_data)?;
|
||||
}
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,65 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use crash_helper_common::{errors::IPCError, ignore_eintr, IPCEvent};
|
||||
use nix::poll::{poll, PollFd, PollFlags, PollTimeout};
|
||||
|
||||
use super::IPCServer;
|
||||
|
||||
impl IPCServer {
|
||||
pub fn wait_for_events(&mut self) -> Result<Vec<IPCEvent>, IPCError> {
|
||||
let mut pollfds = Vec::with_capacity(self.connections.len());
|
||||
pollfds.extend(
|
||||
self.connections.iter().map(|connection| {
|
||||
PollFd::new(connection.connector.as_raw_ref(), PollFlags::POLLIN)
|
||||
}),
|
||||
);
|
||||
|
||||
let mut events = Vec::<IPCEvent>::new();
|
||||
let mut num_events =
|
||||
ignore_eintr!(poll(&mut pollfds, PollTimeout::NONE)).map_err(IPCError::System)?;
|
||||
|
||||
for (index, pollfd) in pollfds.iter().enumerate() {
|
||||
// revents() returns None only if the kernel sends back data
|
||||
// that nix does not understand, we can safely assume this
|
||||
// never happens in practice hence the unwrap().
|
||||
let revents = pollfd.revents().unwrap();
|
||||
|
||||
if revents.contains(PollFlags::POLLHUP) {
|
||||
events.push(IPCEvent::Disconnect(index));
|
||||
// If a process was disconnected then skip all further
|
||||
// processing of the socket. This wouldn't matter normally,
|
||||
// but on macOS calling recvmsg() on a hung-up socket seems
|
||||
// to trigger a kernel panic, one we've already encountered
|
||||
// in the past. Doing things this way avoids the panic
|
||||
// while having no real downsides.
|
||||
continue;
|
||||
}
|
||||
|
||||
if revents.contains(PollFlags::POLLIN) {
|
||||
// SAFETY: The index is guaranteed to be >0 and within
|
||||
// the bounds of the connections array.
|
||||
let connection = unsafe { self.connections.get_unchecked(index) };
|
||||
let header = connection.connector.recv_header();
|
||||
if let Ok(header) = header {
|
||||
// Note that if we encounter a failure we don't propagate
|
||||
// it, when the socket gets disconnected we'll get a
|
||||
// POLLHUP event anyway so deal with disconnections there
|
||||
// instead of here.
|
||||
events.push(IPCEvent::Header(index, header));
|
||||
}
|
||||
}
|
||||
|
||||
if !revents.is_empty() {
|
||||
num_events -= 1;
|
||||
|
||||
if num_events == 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(events)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,96 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use std::convert::TryInto;
|
||||
|
||||
use crash_helper_common::{errors::IPCError, IPCEvent};
|
||||
use log::error;
|
||||
use windows_sys::Win32::{
|
||||
Foundation::{ERROR_BROKEN_PIPE, FALSE, HANDLE, WAIT_OBJECT_0},
|
||||
System::{
|
||||
SystemServices::MAXIMUM_WAIT_OBJECTS,
|
||||
Threading::{WaitForMultipleObjects, INFINITE},
|
||||
},
|
||||
};
|
||||
|
||||
use super::IPCServer;
|
||||
|
||||
impl IPCServer {
|
||||
pub fn wait_for_events(&mut self) -> Result<Vec<IPCEvent>, IPCError> {
|
||||
for connection in self.connections.iter_mut() {
|
||||
// TODO: We might get a broken pipe error here which would cause us to
|
||||
// fail instead of just dropping the disconnected connection.
|
||||
connection.connector.sched_recv_header()?;
|
||||
}
|
||||
|
||||
let native_events = self.collect_events();
|
||||
|
||||
// SAFETY: This is less than MAXIMUM_WAIT_OBJECTS
|
||||
let native_events_len: u32 = unsafe { native_events.len().try_into().unwrap_unchecked() };
|
||||
|
||||
let res = unsafe {
|
||||
WaitForMultipleObjects(
|
||||
native_events_len,
|
||||
native_events.as_ptr(),
|
||||
FALSE, // bWaitAll
|
||||
INFINITE,
|
||||
)
|
||||
};
|
||||
|
||||
if res >= (WAIT_OBJECT_0 + native_events_len) {
|
||||
return Err(IPCError::WaitingFailure(None));
|
||||
}
|
||||
|
||||
let index = (res - WAIT_OBJECT_0) as usize;
|
||||
|
||||
let mut events = Vec::<IPCEvent>::new();
|
||||
if index == 0 {
|
||||
if let Ok(connector) = self.listener.accept() {
|
||||
events.push(IPCEvent::Connect(connector));
|
||||
}
|
||||
} else {
|
||||
let index = index - 1;
|
||||
// SAFETY: The index is guaranteed to be within the bounds of the connections array.
|
||||
let connection = unsafe { self.connections.get_unchecked_mut(index) };
|
||||
let header = connection.connector.collect_header();
|
||||
|
||||
match header {
|
||||
Ok(header) => {
|
||||
events.push(IPCEvent::Header(index, header));
|
||||
}
|
||||
Err(error) => match error {
|
||||
IPCError::System(_code @ ERROR_BROKEN_PIPE) => {
|
||||
events.push(IPCEvent::Disconnect(index));
|
||||
}
|
||||
_ => return Err(error),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Ok(events)
|
||||
}
|
||||
|
||||
/// This currently returns a vector that is no longer than
|
||||
/// `MAXIMUM_WAIT_OBJECTS`, so its contents can be safely passed to
|
||||
/// a `WaitForMultipleObjects()` call.
|
||||
fn collect_events(&self) -> Vec<HANDLE> {
|
||||
let mut events = Vec::with_capacity(1 + self.connections.len());
|
||||
|
||||
events.push(self.listener.event_raw_handle());
|
||||
for connection in self.connections.iter() {
|
||||
events.push(connection.connector.event_raw_handle());
|
||||
}
|
||||
|
||||
// HACK: When we hit this limit we should be splitting this list in
|
||||
// multiple groups of at most MAXIMUM_WAIT_OBJECTS objects and have
|
||||
// several threads wait on the groups, then wait on the threads
|
||||
// themselves.
|
||||
if events.len() > MAXIMUM_WAIT_OBJECTS.try_into().unwrap() {
|
||||
error!("More than {MAXIMUM_WAIT_OBJECTS} processes connecting to the crash helper");
|
||||
events.truncate(MAXIMUM_WAIT_OBJECTS.try_into().unwrap());
|
||||
}
|
||||
|
||||
events
|
||||
}
|
||||
}
|
||||
|
|
@ -13,13 +13,16 @@ mod phc;
|
|||
|
||||
#[cfg(not(target_os = "android"))]
|
||||
use crash_helper_common::Pid;
|
||||
#[cfg(target_os = "android")]
|
||||
use crash_helper_common::RawAncillaryData;
|
||||
use crash_helper_common::{BreakpadData, BreakpadRawData, IPCConnector, IPCListener};
|
||||
use std::ffi::{c_char, CStr, OsString};
|
||||
use std::{
|
||||
ffi::{c_char, CStr, OsString},
|
||||
fmt::Display,
|
||||
};
|
||||
|
||||
use crash_generation::CrashGenerator;
|
||||
use ipc_server::{IPCServer, IPCServerState};
|
||||
#[cfg(target_os = "android")]
|
||||
use std::os::fd::{FromRawFd, OwnedFd, RawFd};
|
||||
|
||||
/// Runs the crash generator process logic, this includes the IPC used by
|
||||
/// processes to signal that they crashed, the IPC used to retrieve crash
|
||||
|
|
@ -52,26 +55,25 @@ pub unsafe extern "C" fn crash_generator_logic_desktop(
|
|||
.unwrap();
|
||||
let minidump_path = OsString::from(minidump_path);
|
||||
let listener = unsafe { CStr::from_ptr(listener) };
|
||||
let listener = IPCListener::deserialize(listener, client_pid)
|
||||
.map_err(|error| {
|
||||
log::error!("Could not parse the crash generator's listener (error: {error})");
|
||||
})
|
||||
.unwrap();
|
||||
let listener = unwrap_with_message(
|
||||
IPCListener::deserialize(listener, client_pid),
|
||||
"Could not parse the crash generator's listener",
|
||||
);
|
||||
let pipe = unsafe { CStr::from_ptr(pipe) };
|
||||
let connector = IPCConnector::deserialize(pipe)
|
||||
.map_err(|error| {
|
||||
log::error!("Could not parse the crash generator's connector (error: {error})");
|
||||
})
|
||||
.unwrap();
|
||||
let connector = unwrap_with_message(
|
||||
IPCConnector::deserialize(pipe),
|
||||
"Could not parse the crash generator's connector",
|
||||
);
|
||||
|
||||
let crash_generator = CrashGenerator::new(breakpad_data, minidump_path)
|
||||
.map_err(|error| {
|
||||
log::error!("Could not create the crash generator (error: {error})");
|
||||
error
|
||||
})
|
||||
.unwrap();
|
||||
let crash_generator = unwrap_with_message(
|
||||
CrashGenerator::new(breakpad_data, minidump_path),
|
||||
"Could not create the crash generator",
|
||||
);
|
||||
|
||||
let ipc_server = IPCServer::new(listener, connector);
|
||||
let ipc_server = unwrap_with_message(
|
||||
IPCServer::new(listener, connector),
|
||||
"Could not create the IPC server",
|
||||
);
|
||||
|
||||
main_loop(ipc_server, crash_generator)
|
||||
}
|
||||
|
|
@ -92,7 +94,7 @@ pub unsafe extern "C" fn crash_generator_logic_desktop(
|
|||
pub unsafe extern "C" fn crash_generator_logic_android(
|
||||
breakpad_data: BreakpadRawData,
|
||||
minidump_path: *const c_char,
|
||||
pipe: RawFd,
|
||||
pipe: RawAncillaryData,
|
||||
) {
|
||||
logging::init();
|
||||
|
||||
|
|
@ -102,25 +104,28 @@ pub unsafe extern "C" fn crash_generator_logic_android(
|
|||
.into_string()
|
||||
.unwrap();
|
||||
let minidump_path = OsString::from(minidump_path);
|
||||
let crash_generator = CrashGenerator::new(breakpad_data, minidump_path)
|
||||
.map_err(|error| {
|
||||
log::error!("Could not create the crash generator (error: {error})");
|
||||
error
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let listener = IPCListener::new(0).unwrap();
|
||||
let pipe = unsafe { OwnedFd::from_raw_fd(pipe) };
|
||||
let connector = IPCConnector::from_fd(pipe)
|
||||
.map_err(|error| {
|
||||
log::error!("Could not use the pipe (error: {error})");
|
||||
})
|
||||
.unwrap();
|
||||
let ipc_server = IPCServer::new(listener, connector);
|
||||
|
||||
// On Android the main thread is used to respond to the intents so we
|
||||
// can't block it. Run the crash generation loop in a separate thread.
|
||||
let _ = std::thread::spawn(move || main_loop(ipc_server, crash_generator));
|
||||
let _ = std::thread::spawn(move || {
|
||||
let crash_generator = unwrap_with_message(
|
||||
CrashGenerator::new(breakpad_data, minidump_path),
|
||||
"Could not create the crash generator",
|
||||
);
|
||||
|
||||
let listener = IPCListener::new(0).unwrap();
|
||||
// SAFETY: The `pipe` file descriptor passed in from the caller is
|
||||
// guaranteed to be valid.
|
||||
let connector = unwrap_with_message(
|
||||
unsafe { IPCConnector::from_raw_ancillary(pipe) },
|
||||
"Could not use the pipe",
|
||||
);
|
||||
let ipc_server = unwrap_with_message(
|
||||
IPCServer::new(listener, connector),
|
||||
"Could not create the IPC server",
|
||||
);
|
||||
main_loop(ipc_server, crash_generator)
|
||||
});
|
||||
}
|
||||
|
||||
fn main_loop(mut ipc_server: IPCServer, mut crash_generator: CrashGenerator) -> i32 {
|
||||
|
|
@ -178,3 +183,13 @@ fn daemonize() {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn unwrap_with_message<T, E: Display>(res: Result<T, E>, error_string: &str) -> T {
|
||||
match res {
|
||||
Ok(value) => value,
|
||||
Err(error) => {
|
||||
log::error!("{error_string} (error: {error})");
|
||||
panic!("{} (error: {})", error_string, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3345,7 +3345,8 @@ CrashPipeType GetChildNotificationPipe() {
|
|||
UniqueFileHandle RegisterChildIPCChannel() {
|
||||
StaticMutexAutoLock lock(gCrashHelperClientMutex);
|
||||
if (gCrashHelperClient) {
|
||||
AncillaryData ipc_endpoint = register_child_ipc_channel(gCrashHelperClient);
|
||||
RawAncillaryData ipc_endpoint =
|
||||
register_child_ipc_channel(gCrashHelperClient);
|
||||
return UniqueFileHandle{ipc_endpoint};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -91,7 +91,10 @@ XPCOMUtils.defineLazyPreferenceGetter(
|
|||
);
|
||||
|
||||
ChromeUtils.defineLazyGetter(lazy, "gLocalization", () => {
|
||||
return new Localization(["toolkit/global/browser-utils.ftl"], true);
|
||||
return new Localization(
|
||||
["toolkit/global/browser-utils.ftl", "toolkit/downloads/downloadUtils.ftl"],
|
||||
true
|
||||
);
|
||||
});
|
||||
|
||||
function stringPrefToSet(prefVal) {
|
||||
|
|
@ -246,8 +249,36 @@ export var BrowserUtils = {
|
|||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Show a URI in the UI in a user-friendly (but security-sensitive) way.
|
||||
*
|
||||
* @param {nsIURI} uri
|
||||
* @param {object} [options={}]
|
||||
* @param {boolean} [options.showInsecureHTTP=false]
|
||||
* Whether to show "http://" for insecure HTTP URLs.
|
||||
* @param {boolean} [options.showWWW=false]
|
||||
* Whether to show "www." for URLs that have it.
|
||||
* @param {boolean} [options.onlyBaseDomain=false]
|
||||
* Whether to show only the base domain (eTLD+1) for HTTP(S) URLs.
|
||||
* @param {boolean} [options.showFilenameForLocalURIs=false]
|
||||
* If false (default), will show a protocol-specific label for local
|
||||
* URIs (file:, chrome:, resource:, moz-src:, jar:).
|
||||
* Otherwise, will show the filename for such URIs. Only use 'true' if
|
||||
* the context in which the URI is being represented is not security-
|
||||
* critical.
|
||||
*/
|
||||
formatURIForDisplay(uri, options = {}) {
|
||||
let { showInsecureHTTP = false } = options;
|
||||
let {
|
||||
showInsecureHTTP = false,
|
||||
showWWW = false,
|
||||
onlyBaseDomain = false,
|
||||
showFilenameForLocalURIs = false,
|
||||
} = options;
|
||||
// For moz-icon and jar etc. which wrap nsIURLs, if we want to show the
|
||||
// actual filename, unwrap:
|
||||
if (uri && uri instanceof Ci.nsINestedURI && showFilenameForLocalURIs) {
|
||||
return this.formatURIForDisplay(uri.innermostURI, options);
|
||||
}
|
||||
switch (uri.scheme) {
|
||||
case "view-source": {
|
||||
let innerURI = uri.spec.substring("view-source:".length);
|
||||
|
|
@ -257,8 +288,14 @@ export var BrowserUtils = {
|
|||
// Fall through.
|
||||
case "https": {
|
||||
let host = uri.displayHostPort;
|
||||
if (!showInsecureHTTP && host.startsWith("www.")) {
|
||||
let removeSubdomains =
|
||||
!showInsecureHTTP &&
|
||||
(onlyBaseDomain || (!showWWW && host.startsWith("www.")));
|
||||
if (removeSubdomains) {
|
||||
host = Services.eTLD.getSchemelessSite(uri);
|
||||
if (uri.port != -1) {
|
||||
host += ":" + uri.port;
|
||||
}
|
||||
}
|
||||
if (showInsecureHTTP && uri.scheme == "http") {
|
||||
return "http://" + host;
|
||||
|
|
@ -269,7 +306,7 @@ export var BrowserUtils = {
|
|||
return "about:" + uri.filePath;
|
||||
case "blob":
|
||||
try {
|
||||
let url = new URL(uri.specIgnoringRef);
|
||||
let url = URL.fromURI(uri);
|
||||
// _If_ we find a non-null origin, report that.
|
||||
if (url.origin && url.origin != "null") {
|
||||
return this.formatURIStringForDisplay(url.origin, options);
|
||||
|
|
@ -291,8 +328,22 @@ export var BrowserUtils = {
|
|||
}
|
||||
case "chrome":
|
||||
case "resource":
|
||||
case "moz-icon":
|
||||
case "moz-src":
|
||||
case "jar":
|
||||
case "file":
|
||||
if (!showFilenameForLocalURIs) {
|
||||
if (uri.scheme == "file") {
|
||||
return lazy.gLocalization.formatValueSync(
|
||||
"download-utils-done-file-scheme"
|
||||
);
|
||||
}
|
||||
return lazy.gLocalization.formatValueSync(
|
||||
"download-utils-done-scheme",
|
||||
{ scheme: uri.scheme }
|
||||
);
|
||||
}
|
||||
// Otherwise, fall through to show filename...
|
||||
default:
|
||||
try {
|
||||
let url = uri.QueryInterface(Ci.nsIURL);
|
||||
|
|
@ -318,7 +369,7 @@ export var BrowserUtils = {
|
|||
console.error(ex);
|
||||
}
|
||||
}
|
||||
return uri.asciiHost || uri.spec;
|
||||
return uri.spec;
|
||||
},
|
||||
|
||||
// Given a URL returns a (possibly transformed) URL suitable for sharing, or null if
|
||||
|
|
|
|||
|
|
@ -164,6 +164,7 @@ export var ClipboardContextMenu = {
|
|||
let menupopup = aChromeDoc.createXULElement("menupopup");
|
||||
menupopup.id = this.MENU_POPUP_ID;
|
||||
menupopup.setAttribute("tabspecific", "true");
|
||||
menupopup.setAttribute("locationspecific", "true");
|
||||
menupopup.appendChild(menuitem);
|
||||
return menupopup;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -9,7 +9,10 @@ let tempFile = new FileUtils.File(PathUtils.tempDir);
|
|||
const TEST_LOCAL_FILE_NAME = "hello.txt";
|
||||
tempFile.append(TEST_LOCAL_FILE_NAME);
|
||||
|
||||
const gL10n = new Localization(["toolkit/global/browser-utils.ftl"], true);
|
||||
const gL10n = new Localization(
|
||||
["toolkit/global/browser-utils.ftl", "toolkit/downloads/downloadUtils.ftl"],
|
||||
true
|
||||
);
|
||||
const DATA_URL_EXPECTED_STRING = gL10n.formatValueSync(
|
||||
"browser-utils-url-data"
|
||||
);
|
||||
|
|
@ -19,6 +22,10 @@ const EXTENSION_URL_EXPECTED_STRING = gL10n.formatValueSync(
|
|||
{ extension: EXTENSION_NAME }
|
||||
);
|
||||
|
||||
const FILE_URL_EXPECTED_STRING = gL10n.formatValueSync(
|
||||
"download-utils-done-file-scheme"
|
||||
);
|
||||
|
||||
const { AddonTestUtils } = ChromeUtils.importESModule(
|
||||
"resource://testing-common/AddonTestUtils.sys.mjs"
|
||||
);
|
||||
|
|
@ -88,7 +95,7 @@ const HTTP_TESTS = [
|
|||
output: "www.co.uk",
|
||||
},
|
||||
|
||||
// Other sudomains should be kept:
|
||||
// Other subdomains should be kept:
|
||||
{
|
||||
input: "https://webmail.example.co.uk",
|
||||
output: "webmail.example.co.uk",
|
||||
|
|
@ -158,6 +165,15 @@ const TESTS = [
|
|||
input: "data:text/html,42",
|
||||
output: DATA_URL_EXPECTED_STRING,
|
||||
},
|
||||
|
||||
{
|
||||
input: `moz-icon:${Services.io.newFileURI(tempFile).spec}`,
|
||||
output: tempFile.leafName,
|
||||
},
|
||||
{
|
||||
input: "moz-icon://.extension?size=16",
|
||||
output: "moz-icon://.extension?size=16",
|
||||
},
|
||||
];
|
||||
|
||||
add_setup(async () => {
|
||||
|
|
@ -220,7 +236,9 @@ const { BrowserUtils } = ChromeUtils.importESModule(
|
|||
add_task(async function test_checkStringFormatting() {
|
||||
for (let { input, output } of TESTS) {
|
||||
Assert.equal(
|
||||
BrowserUtils.formatURIStringForDisplay(input),
|
||||
BrowserUtils.formatURIStringForDisplay(input, {
|
||||
showFilenameForLocalURIs: true,
|
||||
}),
|
||||
output,
|
||||
`String ${input} formatted for output should match`
|
||||
);
|
||||
|
|
@ -231,13 +249,56 @@ add_task(async function test_checkURIFormatting() {
|
|||
for (let { input, output } of TESTS) {
|
||||
let uri = Services.io.newURI(input);
|
||||
Assert.equal(
|
||||
BrowserUtils.formatURIForDisplay(uri),
|
||||
BrowserUtils.formatURIForDisplay(uri, {
|
||||
showFilenameForLocalURIs: true,
|
||||
}),
|
||||
output,
|
||||
`URI ${input} formatted for output should match`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
add_task(async function test_checkOnlyBaseDomain() {
|
||||
for (let { input, output } of [
|
||||
{ input: "https://subdomain.example.com/", output: "example.com" },
|
||||
{
|
||||
input: "http://www.city.mikasa.hokkaido.jp/",
|
||||
output: "city.mikasa.hokkaido.jp",
|
||||
},
|
||||
{ input: "https://www.example.co.uk/", output: "example.co.uk" },
|
||||
{
|
||||
input: "mailto:example@subdomain.example.com",
|
||||
output: "mailto:example@subdomain.example.com",
|
||||
},
|
||||
]) {
|
||||
let uri = Services.io.newURI(input);
|
||||
Assert.equal(
|
||||
BrowserUtils.formatURIForDisplay(uri, { onlyBaseDomain: true }),
|
||||
output,
|
||||
`URI ${input} formatted for output should match`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
add_task(async function test_checkLocalFileFormatting() {
|
||||
for (let { input } of TESTS) {
|
||||
let uri = Services.io.newURI(input);
|
||||
if (
|
||||
["file", "chrome", "moz-icon", "resource", "jar"].includes(uri.scheme)
|
||||
) {
|
||||
Assert.equal(
|
||||
BrowserUtils.formatURIForDisplay(uri, {
|
||||
showFilenameForLocalURIs: false,
|
||||
}),
|
||||
uri.scheme == "file"
|
||||
? FILE_URL_EXPECTED_STRING
|
||||
: `${uri.scheme} resource`,
|
||||
`URI ${input} formatted for output should match`
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
add_task(async function test_checkViewSourceFormatting() {
|
||||
for (let { input, output } of HTTP_TESTS) {
|
||||
Assert.equal(
|
||||
|
|
|
|||
|
|
@ -3,8 +3,6 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
|
||||
|
||||
/**
|
||||
* This module provides the DownloadUtils object which contains useful methods
|
||||
* for downloads such as displaying file sizes, transfer times, and download
|
||||
|
|
@ -55,14 +53,6 @@ const TIME_UNITS = [
|
|||
// with TIME_UNITS without the last item
|
||||
const TIME_SIZES = [60, 60, 24];
|
||||
|
||||
const lazy = {};
|
||||
XPCOMUtils.defineLazyServiceGetter(
|
||||
lazy,
|
||||
"IDNService",
|
||||
"@mozilla.org/network/idn-service;1",
|
||||
"nsIIDNService"
|
||||
);
|
||||
|
||||
var localeNumberFormatCache = new Map();
|
||||
function getLocaleNumberFormat(fractionDigits) {
|
||||
if (!localeNumberFormatCache.has(fractionDigits)) {
|
||||
|
|
@ -386,82 +376,6 @@ export var DownloadUtils = {
|
|||
return [dateTimeCompact, dateTimeFull];
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the appropriate display host string for a URI string depending on if
|
||||
* the URI has an eTLD + 1, is an IP address, a local file, or other protocol
|
||||
*
|
||||
* @param aURIString
|
||||
* The URI string to try getting an eTLD + 1, etc.
|
||||
* @return A pair: [display host for the URI string, full host name]
|
||||
*/
|
||||
getURIHost: function DU_getURIHost(aURIString) {
|
||||
// Get a URI that knows about its components
|
||||
let uri = URL.parse(aURIString)?.URI;
|
||||
if (!uri) {
|
||||
return ["", ""];
|
||||
}
|
||||
|
||||
// Get the inner-most uri for schemes like jar:
|
||||
if (uri instanceof Ci.nsINestedURI) {
|
||||
uri = uri.innermostURI;
|
||||
}
|
||||
|
||||
if (uri.schemeIs("blob")) {
|
||||
let origin = URL.fromURI(uri).origin;
|
||||
// Origin can be "null" for blob URIs from a sandbox.
|
||||
if (origin != "null") {
|
||||
// `newURI` can throw (like for null) and throwing here breaks...
|
||||
// a lot of stuff. So let's avoid doing that in case there are other
|
||||
// edgecases we're missing here.
|
||||
try {
|
||||
uri = Services.io.newURI(origin);
|
||||
} catch (ex) {
|
||||
console.error(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let fullHost;
|
||||
try {
|
||||
// Get the full host name; some special URIs fail (data: jar:)
|
||||
fullHost = uri.host;
|
||||
} catch (e) {
|
||||
fullHost = "";
|
||||
}
|
||||
|
||||
let displayHost;
|
||||
try {
|
||||
// This might fail if it's an IP address or doesn't have more than 1 part
|
||||
let baseDomain = Services.eTLD.getBaseDomain(uri);
|
||||
|
||||
// Convert base domain for display
|
||||
displayHost = lazy.IDNService.convertToDisplayIDN(baseDomain);
|
||||
} catch (e) {
|
||||
// Default to the host name
|
||||
displayHost = fullHost;
|
||||
}
|
||||
|
||||
// Check if we need to show something else for the host
|
||||
if (uri.schemeIs("file")) {
|
||||
// Display special text for file protocol
|
||||
displayHost = l10n.formatValueSync("download-utils-done-file-scheme");
|
||||
fullHost = displayHost;
|
||||
} else if (!displayHost.length) {
|
||||
// Got nothing; show the scheme (data: about: moz-icon:)
|
||||
displayHost = l10n.formatValueSync("download-utils-done-scheme", {
|
||||
scheme: uri.scheme,
|
||||
});
|
||||
fullHost = displayHost;
|
||||
} else if (uri.port != -1) {
|
||||
// Tack on the port if it's not the default port
|
||||
let port = ":" + uri.port;
|
||||
displayHost += port;
|
||||
fullHost += port;
|
||||
}
|
||||
|
||||
return [displayHost, fullHost];
|
||||
},
|
||||
|
||||
/**
|
||||
* Converts a number of bytes to the appropriate unit that results in an
|
||||
* internationalized number that needs fewer than 4 digits.
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ add_task(async function test_check_blob_origin_representation() {
|
|||
await check_blob_origin(
|
||||
"data:text/html,<body>Some Text<br>",
|
||||
"(data)",
|
||||
"blob"
|
||||
"(data)"
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -83,16 +83,6 @@ function testFormattedTimeStatus(aSec, aExpected) {
|
|||
Assert.equal(status.l10n.id, aExpected);
|
||||
}
|
||||
|
||||
function testURI(aURI, aDisp, aHost) {
|
||||
dump("URI Test: " + [aURI, aDisp, aHost] + "\n");
|
||||
|
||||
let [disp, host] = DownloadUtils.getURIHost(aURI);
|
||||
|
||||
// Make sure we have the right display host and full host
|
||||
Assert.equal(disp, aDisp);
|
||||
Assert.equal(host, aHost);
|
||||
}
|
||||
|
||||
function testGetReadableDates(aDate, aCompactValue) {
|
||||
const now = new Date(2000, 11, 31, 11, 59, 59);
|
||||
|
||||
|
|
@ -373,26 +363,5 @@ function run_test() {
|
|||
testFormattedTimeStatus(0, "downloading-file-opens-in-seconds-2");
|
||||
testFormattedTimeStatus(30, "downloading-file-opens-in-seconds-2");
|
||||
|
||||
testURI("http://www.mozilla.org/", "mozilla.org", "www.mozilla.org");
|
||||
testURI(
|
||||
"http://www.city.mikasa.hokkaido.jp/",
|
||||
"city.mikasa.hokkaido.jp",
|
||||
"www.city.mikasa.hokkaido.jp"
|
||||
);
|
||||
testURI("data:text/html,Hello World", "data resource", "data resource");
|
||||
testURI(
|
||||
"jar:http://www.mozilla.com/file!/magic",
|
||||
"mozilla.com",
|
||||
"www.mozilla.com"
|
||||
);
|
||||
testURI("file:///C:/Cool/Stuff/", "local file", "local file");
|
||||
// Don't test for moz-icon if we don't have a protocol handler for it (e.g. b2g):
|
||||
if ("@mozilla.org/network/protocol;1?name=moz-icon" in Cc) {
|
||||
testURI("moz-icon:file:///test.extension", "local file", "local file");
|
||||
testURI("moz-icon://.extension", "moz-icon resource", "moz-icon resource");
|
||||
}
|
||||
testURI("about:config", "about resource", "about resource");
|
||||
testURI("invalid.uri", "", "");
|
||||
|
||||
testAllGetReadableDates();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -115,7 +115,11 @@ nsAlertsIconListener::nsAlertsIconListener(
|
|||
mBackend(aBackend),
|
||||
mAlertNotification(aAlertNotification) {
|
||||
if (!libNotifyHandle && !libNotifyNotAvail) {
|
||||
#ifdef __OpenBSD__
|
||||
libNotifyHandle = dlopen("libnotify.so", RTLD_LAZY);
|
||||
#else
|
||||
libNotifyHandle = dlopen("libnotify.so.4", RTLD_LAZY);
|
||||
#endif
|
||||
if (!libNotifyHandle) {
|
||||
libNotifyHandle = dlopen("libnotify.so.1", RTLD_LAZY);
|
||||
if (!libNotifyHandle) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue