summaryrefslogtreecommitdiffstats
path: root/toolkit/crashreporter/client/app/src/main.rs
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/crashreporter/client/app/src/main.rs')
-rw-r--r--toolkit/crashreporter/client/app/src/main.rs229
1 files changed, 229 insertions, 0 deletions
diff --git a/toolkit/crashreporter/client/app/src/main.rs b/toolkit/crashreporter/client/app/src/main.rs
new file mode 100644
index 0000000000..07e1b04cb8
--- /dev/null
+++ b/toolkit/crashreporter/client/app/src/main.rs
@@ -0,0 +1,229 @@
+/* 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/. */
+
+//! The crash reporter application.
+//!
+//! # Architecture
+//! The application uses a simple declarative [UI model](ui::model) to define the UI. This model
+//! contains [data bindings](data) which provide the dynamic behaviors of the UI. Separate UI
+//! implementations for linux (gtk), macos (cocoa), and windows (win32) exist, as well as a test UI
+//! which is virtual (no actual interface is presented) but allows runtime introspection.
+//!
+//! # Mocking
+//! This application contains mock interfaces for all the `std` functions it uses which interact
+//! with the host system. You can see their implementation in [`crate::std`]. To enable mocking,
+//! use the `mock` feature or build with `MOZ_CRASHREPORTER_MOCK` set (which, in `build.rs`, is
+//! translated to a `cfg` option). *Note* that this cfg _must_ be enabled when running tests.
+//! Unfortunately it is not possible to detect whether tests are being built in `build.rs, which
+//! is why a feature needed to be made in the first place (it is enabled automatically when running
+//! `mach rusttests`).
+//!
+//! Currently the input program configuration which is mocked when running the application is fixed
+//! (see the [`main`] implementation in this file). If needed in the future, it would be nice to
+//! extend this to allow runtime tweaking.
+//!
+//! # Development
+//! Because of the mocking support previously mentioned, in generally any `std` imports should
+//! actually use `crate::std`. If mocked functions/types are missing, they should be added with
+//! appropriate mocking hooks.
+
+// Use the WINDOWS windows subsystem. This prevents a console window from opening with the
+// application.
+#![cfg_attr(windows, windows_subsystem = "windows")]
+
+use crate::std::sync::Arc;
+use anyhow::Context;
+use config::Config;
+
+/// cc is short for Clone Capture, a shorthand way to clone a bunch of values before an expression
+/// (particularly useful for closures).
+///
+/// It is defined here to allow it to be used in all submodules (textual scope lookup).
+macro_rules! cc {
+ ( ($($c:ident),*) $e:expr ) => {
+ {
+ $(let $c = $c.clone();)*
+ $e
+ }
+ }
+}
+
+mod async_task;
+mod config;
+mod data;
+mod lang;
+mod logging;
+mod logic;
+mod net;
+mod process;
+mod settings;
+mod std;
+mod thread_bound;
+mod ui;
+
+#[cfg(test)]
+mod test;
+
+#[cfg(not(mock))]
+fn main() {
+ let log_target = logging::init();
+
+ let mut config = Config::new();
+ let config_result = config.read_from_environment();
+ config.log_target = Some(log_target);
+
+ let mut config = Arc::new(config);
+
+ let result = config_result.and_then(|()| {
+ let attempted_send = try_run(&mut config)?;
+ if !attempted_send {
+ // Exited without attempting to send the crash report; delete files.
+ config.delete_files();
+ }
+ Ok(())
+ });
+
+ if let Err(message) = result {
+ // TODO maybe errors should also delete files?
+ log::error!("exiting with error: {message}");
+ if !config.auto_submit {
+ // Only show a dialog if auto_submit is disabled.
+ ui::error_dialog(&config, message);
+ }
+ std::process::exit(1);
+ }
+}
+
+#[cfg(mock)]
+fn main() {
+ // TODO it'd be nice to be able to set these values at runtime in some way when running the
+ // mock application.
+
+ use crate::std::{
+ fs::{MockFS, MockFiles},
+ mock,
+ process::Command,
+ };
+ const MOCK_MINIDUMP_EXTRA: &str = r#"{
+ "Vendor": "FooCorp",
+ "ProductName": "Bar",
+ "ReleaseChannel": "release",
+ "BuildID": "1234",
+ "StackTraces": {
+ "status": "OK"
+ },
+ "Version": "100.0",
+ "ServerURL": "https://reports.example",
+ "TelemetryServerURL": "https://telemetry.example",
+ "TelemetryClientId": "telemetry_client",
+ "TelemetrySessionId": "telemetry_session",
+ "URL": "https://url.example"
+ }"#;
+
+ // Actual content doesn't matter, aside from the hash that is generated.
+ const MOCK_MINIDUMP_FILE: &[u8] = &[1, 2, 3, 4];
+ const MOCK_CURRENT_TIME: &str = "2004-11-09T12:34:56Z";
+ const MOCK_PING_UUID: uuid::Uuid = uuid::Uuid::nil();
+ const MOCK_REMOTE_CRASH_ID: &str = "8cbb847c-def2-4f68-be9e-000000000000";
+
+ // Create a default set of files which allow successful operation.
+ let mock_files = MockFiles::new();
+ mock_files
+ .add_file("minidump.dmp", MOCK_MINIDUMP_FILE)
+ .add_file("minidump.extra", MOCK_MINIDUMP_EXTRA);
+
+ // Create a default mock environment which allows successful operation.
+ let mut mock = mock::builder();
+ mock.set(
+ Command::mock("work_dir/minidump-analyzer"),
+ Box::new(|_| Ok(crate::std::process::success_output())),
+ )
+ .set(
+ Command::mock("work_dir/pingsender"),
+ Box::new(|_| Ok(crate::std::process::success_output())),
+ )
+ .set(
+ Command::mock("curl"),
+ Box::new(|_| {
+ let mut output = crate::std::process::success_output();
+ output.stdout = format!("CrashID={MOCK_REMOTE_CRASH_ID}").into();
+ // Network latency.
+ std::thread::sleep(std::time::Duration::from_secs(2));
+ Ok(output)
+ }),
+ )
+ .set(MockFS, mock_files.clone())
+ .set(
+ crate::std::env::MockCurrentExe,
+ "work_dir/crashreporter".into(),
+ )
+ .set(
+ crate::std::time::MockCurrentTime,
+ time::OffsetDateTime::parse(
+ MOCK_CURRENT_TIME,
+ &time::format_description::well_known::Rfc3339,
+ )
+ .unwrap()
+ .into(),
+ )
+ .set(mock::MockHook::new("ping_uuid"), MOCK_PING_UUID);
+
+ let result = mock.run(|| {
+ let mut cfg = Config::new();
+ cfg.data_dir = Some("data_dir".into());
+ cfg.events_dir = Some("events_dir".into());
+ cfg.ping_dir = Some("ping_dir".into());
+ cfg.dump_file = Some("minidump.dmp".into());
+ cfg.restart_command = Some("mockfox".into());
+ cfg.strings = Some(lang::load().unwrap());
+ let mut cfg = Arc::new(cfg);
+ try_run(&mut cfg)
+ });
+
+ if let Err(e) = result {
+ log::error!("exiting with error: {e}");
+ std::process::exit(1);
+ }
+}
+
+fn try_run(config: &mut Arc<Config>) -> anyhow::Result<bool> {
+ if config.dump_file.is_none() {
+ if !config.auto_submit {
+ Err(anyhow::anyhow!(config.string("crashreporter-information")))
+ } else {
+ Ok(false)
+ }
+ } else {
+ // Run minidump-analyzer to gather stack traces.
+ {
+ let analyzer_path = config.sibling_program_path("minidump-analyzer");
+ let mut cmd = crate::process::background_command(&analyzer_path);
+ if config.dump_all_threads {
+ cmd.arg("--full");
+ }
+ cmd.arg(config.dump_file());
+ let output = cmd
+ .output()
+ .with_context(|| config.string("crashreporter-error-minidump-analyzer"))?;
+ if !output.status.success() {
+ log::warn!(
+ "minidump-analyzer failed to run ({});\n\nstderr: {}\n\nstdout: {}",
+ output.status,
+ String::from_utf8_lossy(&output.stderr),
+ String::from_utf8_lossy(&output.stdout),
+ );
+ }
+ }
+
+ let extra = {
+ // Perform a few things which may change the config, then treat is as immutable.
+ let config = Arc::get_mut(config).expect("unexpected config references");
+ let extra = config.load_extra_file()?;
+ config.move_crash_data_to_pending()?;
+ extra
+ };
+
+ logic::ReportCrash::new(config.clone(), extra)?.run()
+ }
+}