icecat: add release icecat-140.7.0-1gnu1 for ecne

This commit is contained in:
Ark74 2026-01-18 00:07:02 -06:00
parent 7d0f5dab3b
commit 30225f2e73
156 changed files with 9131 additions and 4525 deletions

View file

@ -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;

View file

@ -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");

View file

@ -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();

View file

@ -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",

View file

@ -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);
}
);
});

View 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);
}

View file

@ -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);

View file

@ -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"
);

View file

@ -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(()),
})
}

View file

@ -34,7 +34,6 @@ impl CrashHelperClient {
Ok(CrashHelperClient {
connector: client_endpoint,
spawner_thread: None,
helper_process: Some(()),
})
}

View file

@ -52,7 +52,6 @@ impl CrashHelperClient {
Ok(CrashHelperClient {
connector: client_endpoint,
spawner_thread: Some(spawner_thread),
helper_process: None,
})
}

View file

@ -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",

View file

@ -9,7 +9,7 @@ use std::{
os::unix::ffi::OsStringExt,
};
use crate::{errors::MessageError, BreakpadString};
use crate::{messages::MessageError, BreakpadString};
use super::BreakpadChar;

View file

@ -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 {

View file

@ -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),
}

View file

@ -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 *
*****************************************************************************/

View file

@ -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)?;

View file

@ -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,

View file

@ -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;

View file

@ -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)
}
}

View file

@ -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

View file

@ -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 *
*****************************************************************************/

View file

@ -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.

View file

@ -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;

View file

@ -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)
}
}

View file

@ -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;
}
}
}
}

View file

@ -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;

View file

@ -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(

View file

@ -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;

View file

@ -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)
}

View file

@ -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)
}

View file

@ -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);
}
}

View file

@ -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");

View file

@ -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(())
}

View file

@ -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)
}
}

View file

@ -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
}
}

View file

@ -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);
}
}
}

View file

@ -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};
}

View file

@ -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

View file

@ -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;
},

View file

@ -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(

View file

@ -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.

View file

@ -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)"
);
});

View file

@ -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();
}

View file

@ -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) {