/* This Source Code Form is subject to the terms of the Mozilla Public *License,v.2.0.IfacopyoftheMPLwasnotdistributedwiththis
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/// Returns whether an attempt was made to send the report. pubfn run(mutself) -> anyhow::Result<bool> { self.memtest_according_to_settings(); self.set_log_file(); self.set_extra_context(); let hash = self.compute_minidump_hash(); self.send_crash_ping(); iflet Err(e) = self.update_events_file(hash.as_deref()) {
log::warn!("failed to update events file: {e:#}");
} self.check_eol_version()?;
if !self.config.auto_submit { self.run_ui();
} else {
anyhow::ensure!(
block_on(self.try_send()).unwrap_or(false), "failed to send report"
);
}
Ok(self.attempted_to_send.load(Relaxed))
}
/// Set the log file based on the current configured paths. /// /// This is the earliest that this can occur as the configuration data dir may be set based on /// fields in the extra file. fn set_log_file(&self) { iflet Some(log_target) = &self.config.log_target {
log_target.set_file(&self.config.data_dir().join("submit.log"));
}
}
/// Set the process type and crash time, if not already set. /// /// NOTE: this assumes that the crashreporter client (in the default mode) is only used to send /// main process crashes. fn set_extra_context(&mutself) { let Some(obj) = self.extra.as_object_mut() else {
log::error!("expected extra data to be an object"); return;
};
obj.entry("ProcessType").or_insert("main".into()); if !obj.contains_key("CrashTime") { iflet Ok(time) = crate::std::time::SystemTime::now()
.duration_since(crate::std::time::SystemTime::UNIX_EPOCH)
{
obj.insert("CrashTime".into(), time.as_secs().to_string().into());
}
}
}
/// Compute the SHA256 hash of the minidump file contents, set it in the extra file, and return /// it as a hex string. fn compute_minidump_hash(&mutself) -> Option<String> { match sha256_hash_file(self.config.dump_file()) {
Ok(hash) => { self.extra["MinidumpSha256Hash"] = hash.clone().into();
Some(hash)
}
Err(e) => {
log::warn!("failed to compute minidump hash: {e:#}");
None
}
}
}
let event_version = read_field("event version")?; let time = read_field("time")?; let uuid = read_field("uuid")?; let data = serde_json::from_reader(reader)?;
Ok(EventsFile {
event_version,
time,
uuid,
data,
})
}
// Write altered events file. let file = std::fs::File::create(&event_path).with_context(|| {
format!("failed to truncate event file at {}", event_path.display())
})?;
events_file
.write(file)
.with_context(|| format!("failed to write event file at {}", event_path.display()))
}
/// Check whether the version of the software that generated the crash is EOL. fn check_eol_version(&self) -> anyhow::Result<()> { iflet Some(version) = self.extra["Version"].as_str() { ifself.config.version_eol_file(version).exists() { self.config.delete_files();
anyhow::bail!(self.config.string("crashreporter-error-version-eol"));
}
}
Ok(())
}
/// Save the current settings. fn save_settings(&self) { let result: anyhow::Result<()> = (|| {
Ok(self
.settings
.borrow()
.to_writer(std::fs::File::create(&self.settings_file)?)?)
})(); iflet Err(e) = result {
log::error!("error while saving settings: {e}");
}
}
/// Handle a response from submitting a crash report. /// /// Returns the crash ID to use for the recorded submission event. Errors in this function may /// result in None being returned to consider the crash report submission as a failure even /// though the server did provide a response. fn handle_crash_report_response(
&self,
response: net::report::Response,
) -> anyhow::Result<Option<String>> { iflet Some(version) = response.stop_sending_reports_for { // Create the EOL version file. The content seemingly doesn't matter, but we mimic what // was written by the old crash reporter. iflet Err(e) = std::fs::write(self.config.version_eol_file(&version), "1\n") {
log::warn!("failed to write EOL file: {e}");
}
}
if response.discarded {
log::debug!("response indicated that the report was discarded"); return Ok(None);
}
let Some(crash_id) = response.crash_id else {
log::debug!("response did not provide a crash id"); return Ok(None);
};
// Write the id to the `submitted` directory let submitted_dir = self.config.submitted_crash_dir();
std::fs::create_dir_all(&submitted_dir).with_context(|| {
format!( "failed to create submitted crash directory {}",
submitted_dir.display()
)
})?;
let crash_id_file = submitted_dir.join(format!("{crash_id}.txt"));
letmut file = std::fs::File::create(&crash_id_file).with_context(|| {
format!( "failed to create submitted crash file for {crash_id} ({})",
crash_id_file.display()
)
})?;
// Shadow `std::fmt::Write` to use the correct trait below. usecrate::std::io::Write;
iflet Err(e) = writeln!(
&mut file, "{}", self.config
.build_string("crashreporter-crash-identifier")
.arg("id", &crash_id)
.get()
) {
log::warn!( "failed to write to submitted crash file ({}) for {crash_id}: {e}",
crash_id_file.display()
);
}
/// Write the submission event. /// /// A `None` crash_id indicates that the submission failed. fn write_submission_event(&self, crash_id: Option<String>) -> anyhow::Result<()> { let Some(events_dir) = &self.config.events_dir else { // If there's no events dir, don't do anything. return Ok(());
};
let local_id = self.config.local_dump_id(); let event_path = events_dir.join(format!("{local_id}-submission"));
let unix_epoch_seconds = std::time::SystemTime::now()
.duration_since(std::time::SystemTime::UNIX_EPOCH)
.expect("system time is before the unix epoch")
.as_secs();
std::fs::write(
&event_path,
format!( "crash.submission.1\n{unix_epoch_seconds}\n{local_id}\n{}\n{}",
crash_id.is_some(),
crash_id.as_deref().unwrap_or("")
),
)
.with_context(|| format!("failed to write event to {}", event_path.display()))
}
/// Restart the program. fn restart_process(&self) { self.config.restart_process();
}
/// Run the crash reporting UI. fn run_ui(&mutself) { usecrate::std::{sync::mpsc, thread};
let (logic_send, logic_recv) = mpsc::channel(); let logic_remote_queue = AsyncTask::new(move |f| { // This is best-effort: ignore errors. let _ = logic_send.send(f);
});
let crash_ui = ReportCrashUI::new(
&*self.settings.borrow(), self.config.clone(),
logic_remote_queue.weak(),
);
// Set the UI remote queue. let crash_ui_async_task = crash_ui.async_task(); struct PanicHandler(AsyncTask<ReportCrashUIState>); impl Drop for PanicHandler { fn drop(&mutself) { self.0.push(|_| panic!("logic thread panicked"));
}
} let logic_panic_handler = PanicHandler(crash_ui_async_task.weak()); self.ui = Some(crash_ui_async_task);
// Spawn a separate thread to handle all interactions with `self`. This prevents blocking // the UI for any reason.
// Use a barrier to ensure both threads are live before either starts (ensuring they // can immediately queue work for each other). let barrier = std::sync::Barrier::new(2); let barrier = &barrier;
thread::scope(move |s| { // Move `logic_remote_queue` into this scope so that it will drop when the scope // completes (which will drop the `mpsc::Sender` and cause the logic thread to complete // and join when the UI finishes so the scope can exit). let _logic_remote_queue = logic_remote_queue;
s.spawn(move || { let _logic_panic_handler = logic_panic_handler;
barrier.wait(); whilelet Ok(f) = logic_recv.recv() {
f(self);
}
// Clear the UI remote queue, using it after this point is an error. This also // prevents the panic handler from engaging. // // NOTE we do this here because the compiler can't reason about `self` being safely // accessible after `thread::scope` returns. This is effectively the same result // since the above loop will only exit when `logic_send` is dropped at the end of // the scope. self.ui = None;
// Save settings after UI is closed self.save_settings();
});
barrier.wait();
crash_ui.run()
});
}
/// Overwrite the extra file with the given content. fn update_extra_file(&self, content: &serde_json::Value) -> anyhow::Result<()> { let Some(path) = self.config.extra_file() else { return Ok(());
}; let f = std::fs::File::create(&path)
.with_context(|| format!("failed to truncate {}", path.display().to_string()))?;
serde_json::to_writer(f, content)
.with_context(|| format!("failed to overwrite {}", path.display().to_string()))
}
/// Start or stop a memtest if settings dictate it. fn memtest_according_to_settings(&self) { let memtest_enabled = self.config.run_memtest && self.settings.borrow().test_hardware; letmut memtest = self.memtest.borrow_mut(); if memtest_enabled && memtest.is_none() {
*memtest = Memtest::spawn();
} elseif !memtest_enabled && memtest.is_some() {
*memtest = None;
}
}
}
// These methods may interact with `self.ui`. impl ReportCrash { /// Update the submission details shown in the UI. pubfn update_details(&self) { usecrate::std::fmt::Write;
letmut extra = self.current_extra_data();
modify_extra_for_report(&mut extra);
letmut details = String::new(); letmut entries: Vec<_> = extra.as_object().unwrap().into_iter().collect();
entries.sort_unstable_by_key(|(k, _)| *k); for (key, value) in entries { let _ = write!(details, "{key}: "); iflet Some(v) = value.as_str() {
details.push_str(v);
} else { match serde_json::to_string(value) {
Ok(s) => details.push_str(&s),
Err(e) => { let _ = write!(details, "<serialization error: {e}>");
}
}
} let _ = writeln!(details);
} let _ = writeln!(
details, "{}", self.config.string("crashreporter-report-info")
);
/// Restart the application and send the crash report. pubasyncfn restart(&self) { // Get the program restarted before sending the report. self.restart_process(); let result = self.try_send().await; self.close_window(result.is_some()).await;
}
/// Quit and send the crash report. pubasyncfn quit(&self) { let result = self.try_send().await; self.close_window(result.is_some()).await;
}
asyncfn close_window(&self, report_sent: bool) { if report_sent && !self.config.auto_submit && !cfg!(test) { // Add a delay to allow the user to see the result.
async_scoped_thread(|| std::thread::sleep(std::time::Duration::from_secs(5))).await;
}
self.ui().push(|r| r.close_window.fire(&()));
}
/// Try to send the report. /// /// This function may be called without a UI active (if auto_submit is true), so it will not /// panic if `self.ui` is unset. /// /// Returns whether the report was received (regardless of whether the response was processed /// successfully), if a report could be sent at all (based on the configuration). asyncfn try_send(&self) -> Option<bool> { // Whether the user wants to submit the report or not, we record that we attempted a send // (so to speak), confirming that we got to the point of user input. This will retain the // crash files rather than deleting them. E.g., the user may want to submit it later through // `about:crashes`. self.attempted_to_send.store(true, Relaxed);
let send_report = self.settings.borrow().submit_report;
if !send_report {
log::trace!("not sending report due to user setting"); return None;
}
// Potentially start/stop a memtest now, in case the settings have changed since launch. self.memtest_according_to_settings();
// Incorporate user input into the extra data (which is acknowledged by "submit report" // being enabled). letmut extra = self.current_extra_data();
// Store memtest output. The previous `memtest_according_to_settings()` ensures that we // only add the output if the setting is enabled. iflet Some(memtest) = self.memtest.borrow_mut().take() { iflet Some(ui) = &self.ui {
ui.push(|r| *r.submit_state.borrow_mut() = SubmitState::WaitingHardwareTests);
}
match memtest.collect_output_for_submission() {
Err(e) => log::error!("couldn't get memtest output: {e:#}"),
Ok(s) => extra["MemtestOutput"] = s.into(),
}
}
// Update the extra file, since we may not be deleting it per the `attempted_to_send` // update, and it now matches exactly what we'll be sending. iflet Err(e) = self.update_extra_file(&extra) {
log::error!("failed to update extra file: {e:#}"); // We can proceed in the event of an error; this is best-effort and serves as // insurance in case submission fails.
}
modify_extra_for_report(&mut extra);
// The extra contents cannot change beyond this point. let extra = extra;
// TODO? load proxy info from libgconf on linux
let Some(url) = &self.config.report_url else {
log::warn!("not sending report due to missing report url"); return None;
};
let Some(url) = url.to_str() else {
log::warn!( "not sending report due to provided url containing invalid utf8 characters: {url:?}"
); return None;
};
// Send the report to the server. let memory_file = self.config.memory_file(); let report = net::report::CrashReport {
extra: &extra,
dump_file: self.config.dump_file(),
memory_file: memory_file.as_deref(),
url,
};
let report_response = async_scoped_thread(|| report.send())
.await
.map(Some)
.unwrap_or_else(|e| {
log::error!("failed to send report: {e:#}");
None
});
let report_received = report_response.is_some(); let crash_id = report_response.and_then(|response| { self.handle_crash_report_response(response)
.unwrap_or_else(|e| {
log::error!("failed to handle crash report response: {e}");
None
})
});
// If the response could be handled (indicated by the returned crash id), clean up by // deleting the minidump files. if crash_id.is_some() { self.config.delete_files();
}
// Indicate whether the report was sent successfully, regardless of whether the response // was processed successfully. // // FIXME: this is how the old crash reporter worked, but we might want to change this // behavior. iflet Some(ui) = &self.ui {
ui.push(move |r| {
*r.submit_state.borrow_mut() = if report_received {
SubmitState::Success
} else {
SubmitState::Failure
}
});
}
Some(report_received)
}
/// Form the extra data, taking into account user input. fn current_extra_data(&self) -> serde_json::Value { let include_address = self.settings.borrow().include_url; let comment = if !self.config.auto_submit { self.ui().wait(|r| r.comment.get())
} else {
Default::default()
};
letmut extra = self.extra.clone();
if !comment.is_empty() {
extra["Comments"] = comment.into();
}
if !include_address {
extra.as_object_mut().unwrap().remove("URL");
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.