Fix systemd unloaded unit
This commit is contained in:
+194
-8
@@ -508,14 +508,32 @@ fn result_message(result: &CommandResult) -> String {
|
||||
CommandResult::RestartPreview(_) => {
|
||||
"Restart preview ready; confirm the exact saved-state action.".to_owned()
|
||||
}
|
||||
CommandResult::Restarted(report) => format!(
|
||||
"Restart result: {:?}; failure: {:?}; submitted: {}; may still complete: {}; manual recovery required: {}.",
|
||||
report.stage,
|
||||
report.failure,
|
||||
report.restart_submitted,
|
||||
report.restart_may_still_complete,
|
||||
report.manual_recovery_required
|
||||
),
|
||||
CommandResult::Restarted(report) => {
|
||||
let mut message = format!(
|
||||
"Restart result: {:?}; failure: {:?}; submitted: {}; may still complete: {}; manual recovery required: {}.",
|
||||
report.stage,
|
||||
report.failure,
|
||||
report.restart_submitted,
|
||||
report.restart_may_still_complete,
|
||||
report.manual_recovery_required
|
||||
);
|
||||
if let Some(failure) = report.service_failure {
|
||||
let phase = report
|
||||
.service_phase
|
||||
.map(restart_phase_label)
|
||||
.unwrap_or("service operation");
|
||||
let unit = report
|
||||
.systemd_unit
|
||||
.as_deref()
|
||||
.map(render::safe_text)
|
||||
.unwrap_or_else(|| "configured systemd unit".to_owned());
|
||||
message.push_str(&format!(
|
||||
" Systemd {phase} failed for {unit}: {}.",
|
||||
restart_failure_label(failure)
|
||||
));
|
||||
}
|
||||
message
|
||||
}
|
||||
CommandResult::DeliveryDestination(destination) => format!(
|
||||
"Prepared destination {}. Overwrite confirmation required: {}.",
|
||||
render::safe_text(&destination.path.display().to_string()),
|
||||
@@ -566,6 +584,39 @@ fn result_message(result: &CommandResult) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
fn restart_phase_label(phase: crate::service::RestartPhase) -> &'static str {
|
||||
match phase {
|
||||
crate::service::RestartPhase::Connect => "bus connection",
|
||||
crate::service::RestartPhase::Subscribe => "job subscription",
|
||||
crate::service::RestartPhase::ResolveUnit => "unit resolution",
|
||||
crate::service::RestartPhase::RequestRestart => "restart request",
|
||||
crate::service::RestartPhase::WaitForJob => "job wait",
|
||||
}
|
||||
}
|
||||
|
||||
fn restart_failure_label(failure: crate::service::RestartFailure) -> &'static str {
|
||||
use crate::service::RestartFailure;
|
||||
match failure {
|
||||
RestartFailure::JobFailed => "job failed",
|
||||
RestartFailure::JobDependency => "job dependency failed",
|
||||
RestartFailure::JobTimedOut => "job timed out",
|
||||
RestartFailure::JobCanceled => "job was canceled",
|
||||
RestartFailure::JobSkipped => "job was skipped",
|
||||
RestartFailure::JobResultUnknown => "job returned an unknown result",
|
||||
RestartFailure::BusDisconnected => "D-Bus connection was lost",
|
||||
RestartFailure::ManagerOwnerChanged => "systemd manager owner changed",
|
||||
RestartFailure::SubscriptionFailed => "job subscription failed",
|
||||
RestartFailure::UnitResolutionFailed => "unit could not be loaded",
|
||||
RestartFailure::PermissionDenied => "permission denied",
|
||||
RestartFailure::RequestFailed => "systemd request failed",
|
||||
RestartFailure::WaitTimedOutMayStillComplete => {
|
||||
"operation timed out and may still complete"
|
||||
}
|
||||
RestartFailure::EarlyEventBufferFull => "job event buffer was full",
|
||||
RestartFailure::InvalidJobPath => "systemd returned an invalid job path",
|
||||
}
|
||||
}
|
||||
|
||||
fn process_command_result(
|
||||
result: CommandResult,
|
||||
state: &mut UiState,
|
||||
@@ -1048,11 +1099,15 @@ mod tests {
|
||||
#[test]
|
||||
fn restart_results_render_cancel_success_and_uncertain_failure_honestly() {
|
||||
use crate::app::{RestartFailureKind, RestartReport, RestartStage};
|
||||
use crate::service::{RestartFailure, RestartPhase};
|
||||
|
||||
let report = |stage, failure, restart_submitted, restart_may_still_complete| {
|
||||
CommandResult::Restarted(RestartReport {
|
||||
stage,
|
||||
failure,
|
||||
service_failure: None,
|
||||
service_phase: None,
|
||||
systemd_unit: None,
|
||||
backup_path: None,
|
||||
restart_submitted,
|
||||
restart_may_still_complete,
|
||||
@@ -1076,6 +1131,137 @@ mod tests {
|
||||
assert!(uncertain.contains("ServiceFailed"));
|
||||
assert!(uncertain.contains("may still complete: true"));
|
||||
assert!(uncertain.contains("manual recovery required: true"));
|
||||
|
||||
let detailed = CommandResult::Restarted(RestartReport {
|
||||
stage: RestartStage::Service,
|
||||
failure: Some(RestartFailureKind::ServiceFailed),
|
||||
service_failure: Some(RestartFailure::PermissionDenied),
|
||||
service_phase: Some(RestartPhase::ResolveUnit),
|
||||
systemd_unit: Some("wireguard-vpn.service".to_owned()),
|
||||
backup_path: None,
|
||||
restart_submitted: false,
|
||||
restart_may_still_complete: false,
|
||||
manual_recovery_required: false,
|
||||
});
|
||||
let detailed = result_message(&detailed);
|
||||
assert!(detailed.contains("unit resolution"));
|
||||
assert!(detailed.contains("wireguard-vpn.service"));
|
||||
assert!(detailed.contains("permission denied"));
|
||||
assert!(!detailed.contains("org.freedesktop.DBus.Error"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn every_redacted_service_failure_has_a_readable_tui_category_and_phase() {
|
||||
use crate::app::{RestartFailureKind, RestartReport, RestartStage};
|
||||
use crate::service::{RestartFailure, RestartPhase};
|
||||
|
||||
for (failure, phase, label) in [
|
||||
(
|
||||
RestartFailure::JobFailed,
|
||||
RestartPhase::WaitForJob,
|
||||
"job failed",
|
||||
),
|
||||
(
|
||||
RestartFailure::JobDependency,
|
||||
RestartPhase::WaitForJob,
|
||||
"job dependency failed",
|
||||
),
|
||||
(
|
||||
RestartFailure::JobTimedOut,
|
||||
RestartPhase::WaitForJob,
|
||||
"job timed out",
|
||||
),
|
||||
(
|
||||
RestartFailure::JobCanceled,
|
||||
RestartPhase::WaitForJob,
|
||||
"job was canceled",
|
||||
),
|
||||
(
|
||||
RestartFailure::JobSkipped,
|
||||
RestartPhase::WaitForJob,
|
||||
"job was skipped",
|
||||
),
|
||||
(
|
||||
RestartFailure::JobResultUnknown,
|
||||
RestartPhase::WaitForJob,
|
||||
"job returned an unknown result",
|
||||
),
|
||||
(
|
||||
RestartFailure::BusDisconnected,
|
||||
RestartPhase::Connect,
|
||||
"D-Bus connection was lost",
|
||||
),
|
||||
(
|
||||
RestartFailure::ManagerOwnerChanged,
|
||||
RestartPhase::WaitForJob,
|
||||
"systemd manager owner changed",
|
||||
),
|
||||
(
|
||||
RestartFailure::SubscriptionFailed,
|
||||
RestartPhase::Subscribe,
|
||||
"job subscription failed",
|
||||
),
|
||||
(
|
||||
RestartFailure::UnitResolutionFailed,
|
||||
RestartPhase::ResolveUnit,
|
||||
"unit could not be loaded",
|
||||
),
|
||||
(
|
||||
RestartFailure::PermissionDenied,
|
||||
RestartPhase::RequestRestart,
|
||||
"permission denied",
|
||||
),
|
||||
(
|
||||
RestartFailure::RequestFailed,
|
||||
RestartPhase::RequestRestart,
|
||||
"systemd request failed",
|
||||
),
|
||||
(
|
||||
RestartFailure::WaitTimedOutMayStillComplete,
|
||||
RestartPhase::WaitForJob,
|
||||
"operation timed out and may still complete",
|
||||
),
|
||||
(
|
||||
RestartFailure::EarlyEventBufferFull,
|
||||
RestartPhase::WaitForJob,
|
||||
"job event buffer was full",
|
||||
),
|
||||
(
|
||||
RestartFailure::InvalidJobPath,
|
||||
RestartPhase::WaitForJob,
|
||||
"systemd returned an invalid job path",
|
||||
),
|
||||
] {
|
||||
let report = CommandResult::Restarted(RestartReport {
|
||||
stage: RestartStage::Service,
|
||||
failure: Some(RestartFailureKind::ServiceFailed),
|
||||
service_failure: Some(failure),
|
||||
service_phase: Some(phase),
|
||||
systemd_unit: Some("wg-quick@wg0.service".to_owned()),
|
||||
backup_path: None,
|
||||
restart_submitted: true,
|
||||
restart_may_still_complete: true,
|
||||
manual_recovery_required: true,
|
||||
});
|
||||
let message = result_message(&report);
|
||||
assert!(message.contains(label), "missing {failure:?}: {message}");
|
||||
assert!(message.contains("wg-quick@wg0.service"));
|
||||
assert!(message.contains(restart_phase_label(phase)));
|
||||
assert!(!message.contains("org.freedesktop.DBus.Error"));
|
||||
}
|
||||
|
||||
let hostile_unit = CommandResult::Restarted(RestartReport {
|
||||
stage: RestartStage::Service,
|
||||
failure: Some(RestartFailureKind::ServiceFailed),
|
||||
service_failure: Some(RestartFailure::PermissionDenied),
|
||||
service_phase: Some(RestartPhase::ResolveUnit),
|
||||
systemd_unit: Some("wg0\u{1b}[31m.service".to_owned()),
|
||||
backup_path: None,
|
||||
restart_submitted: false,
|
||||
restart_may_still_complete: false,
|
||||
manual_recovery_required: false,
|
||||
});
|
||||
assert!(!result_message(&hostile_unit).contains('\u{1b}'));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user