1#![allow(unused_must_use)]
2pub mod analyze;
3pub mod data;
4pub mod enums;
5pub mod errors;
6mod file;
7mod journal;
8pub mod journal_data;
9#[cfg(not(feature = "flatpak"))]
10pub mod proxy_switcher;
11pub mod sysdbus;
12pub mod time_handling;
13
14use std::{
15 any::Any,
16 collections::{BTreeMap, BTreeSet, HashMap},
17 fs::File,
18 io::Read,
19 sync::OnceLock,
20 time::{SystemTime, UNIX_EPOCH},
21};
22
23use crate::{
24 enums::{ActiveState, EnablementStatus, LoadState, StartStopMode},
25 file::save_text_to_file,
26 journal_data::Boot,
27 sysdbus::dbus_proxies::{systemd_manager, systemd_manager_async},
28 time_handling::TimestampStyle,
29};
30
31#[cfg(not(feature = "flatpak"))]
32use crate::sysdbus::to_proxy;
33
34use base::{
35 enums::UnitDBusLevel,
36 file::{commander_blocking, flatpak_host_file_path, test_flatpak_spawn},
37 proxy::{DisEnAbleUnitFiles, DisEnAbleUnitFilesResponse},
38};
39use data::{UnitInfo, UnitProcess};
40use enumflags2::{BitFlag, BitFlags};
41use enums::{CleanOption, DependencyType, DisEnableFlags, KillWho, UnitType};
42use errors::SystemdErrors;
43
44use journal_data::{EventRange, JournalEventChunk};
45use log::{error, info, warn};
46
47use tokio::{runtime::Runtime, sync::mpsc};
48use zvariant::{OwnedObjectPath, OwnedValue};
49
50use crate::data::LUnit;
51
52#[derive(Default, Clone, PartialEq, Debug)]
53pub enum BootFilter {
54 #[default]
55 Current,
56 All,
57 Id(String),
58}
59
60#[derive(Clone, Debug)]
61#[allow(unused)]
62pub struct SystemdUnitFile {
63 pub full_name: String,
64 pub status_code: EnablementStatus,
65 pub level: UnitDBusLevel,
66 pub path: String,
67}
68
69#[derive(Debug, Default)]
70pub struct UpdatedUnitInfo {
71 pub primary: String,
72 pub object_path: String,
73 pub description: Option<String>,
74 pub load_state: Option<LoadState>,
75 pub sub_state: Option<String>,
76 pub active_state: Option<ActiveState>,
77 pub unit_file_preset: Option<String>,
78 pub valid_unit_name: bool,
79 pub fragment_path: Option<String>,
80 pub enablement_status: Option<EnablementStatus>,
81}
82
83impl UpdatedUnitInfo {
84 fn new(primary: String, object_path: String) -> Self {
85 Self {
86 primary,
87 object_path,
88 ..Default::default()
89 }
90 }
91}
92
93pub fn runtime() -> &'static Runtime {
94 static RUNTIME: OnceLock<Runtime> = OnceLock::new();
95 RUNTIME.get_or_init(|| Runtime::new().expect("Setting up tokio runtime needs to succeed."))
96}
97
98#[cfg(not(feature = "flatpak"))]
100pub async fn init_proxy_async(run_mode: base::RunMode) {
101 if let Err(e) = sysdbus::init_proxy_async(run_mode).await {
102 error!("Fail starting Proxy. Error {e:?}");
103 }
104}
105
106#[cfg(not(feature = "flatpak"))]
107pub fn shut_down() {
108 sysdbus::shut_down_proxy();
109}
110
111pub fn get_unit_file_state(
112 level: UnitDBusLevel,
113 primary_name: &str,
114) -> Result<EnablementStatus, SystemdErrors> {
115 sysdbus::get_unit_file_state(level, primary_name)
116}
117
118pub async fn list_units_description_and_state_async(
136 level: UnitDBusLevel,
137) -> Result<(Vec<LUnit>, Vec<SystemdUnitFile>), SystemdErrors> {
138 sysdbus::list_units_description_and_state_async(level).await
139}
140
141pub async fn complete_unit_information(
142 units: Vec<(UnitDBusLevel, String, String)>,
143) -> Result<Vec<UpdatedUnitInfo>, SystemdErrors> {
144 sysdbus::complete_unit_information(units).await
145}
146
147pub async fn complete_single_unit_information(
148 primary_name: String,
149 level: UnitDBusLevel,
150 object_path: String,
151) -> Result<Vec<UpdatedUnitInfo>, SystemdErrors> {
152 let units = vec![(level, primary_name, object_path)];
153 sysdbus::complete_unit_information(units).await
154}
155
156pub fn start_unit(
160 level: UnitDBusLevel,
161 unit_name: &str,
162 mode: StartStopMode,
163) -> Result<String, SystemdErrors> {
164 start_unit_name(level, unit_name, mode)
165}
166
167pub fn start_unit_name(
171 level: UnitDBusLevel,
172 unit_name: &str,
173 mode: StartStopMode,
174) -> Result<String, SystemdErrors> {
175 sysdbus::start_unit(level, unit_name, mode)
176}
177
178pub fn stop_unit(
180 level: UnitDBusLevel,
181 primary_name: &str,
182 mode: StartStopMode,
183) -> Result<String, SystemdErrors> {
184 sysdbus::stop_unit(level, primary_name, mode)
185}
186
187pub fn restart_unit(
188 level: UnitDBusLevel,
189 primary_name: &str,
190 mode: StartStopMode,
191) -> Result<String, SystemdErrors> {
192 sysdbus::restart_unit(level, primary_name, mode)
193}
194
195pub fn disenable_unit_file(
196 primary_name: &str,
197 level: UnitDBusLevel,
198 enable_status: EnablementStatus,
199 expected_status: EnablementStatus,
200) -> Result<DisEnAbleUnitFilesResponse, SystemdErrors> {
201 match expected_status {
202 EnablementStatus::Enabled | EnablementStatus::EnabledRuntime => enable_unit_file(
203 level,
204 primary_name,
205 DisEnableFlags::SdSystemdUnitForce.into(),
206 ),
207 _ => {
208 let flags: BitFlags<DisEnableFlags> = if enable_status.is_runtime() {
209 DisEnableFlags::SdSystemdUnitRuntime.into()
210 } else {
211 DisEnableFlags::empty()
212 };
213
214 disable_unit_file(level, primary_name, flags)
215 }
216 }
217}
218
219pub fn enable_unit_file(
220 level: UnitDBusLevel,
221 unit_file: &str,
222 flags: BitFlags<DisEnableFlags>,
223) -> Result<DisEnAbleUnitFilesResponse, SystemdErrors> {
224 #[cfg(not(feature = "flatpak"))]
225 match level {
226 UnitDBusLevel::System | UnitDBusLevel::Both => {
227 if proxy_switcher::PROXY_SWITCHER.enable_unit_file() {
228 proxy_call!(
229 enable_unit_files_with_flags,
230 &[unit_file],
231 flags.bits_c() as u64
232 )
233 } else {
234 systemd_manager()
235 .enable_unit_files_with_flags(&[unit_file], flags.bits_c() as u64)
236 .map_err(|err| err.into())
237 }
238 }
239 UnitDBusLevel::UserSession => sysdbus::dbus_proxies::systemd_manager_session()
240 .enable_unit_files_with_flags(&[unit_file], flags.bits_c() as u64)
241 .map_err(|err| err.into()),
242 }
243
244 #[cfg(feature = "flatpak")]
245 {
246 use crate::sysdbus::dbus_proxies::systemd_manager_blocking;
247 systemd_manager_blocking(level)
248 .enable_unit_files_with_flags(&[unit_file], flags.bits_c() as u64)
249 .map_err(|err| err.into())
250 }
251}
252
253pub fn disable_unit_file(
254 level: UnitDBusLevel,
255 unit_file: &str,
256 flags: BitFlags<DisEnableFlags>,
257) -> Result<DisEnAbleUnitFilesResponse, SystemdErrors> {
258 #[cfg(not(feature = "flatpak"))]
259 match level {
260 UnitDBusLevel::System | UnitDBusLevel::Both => {
261 if proxy_switcher::PROXY_SWITCHER.enable_unit_file() {
262 proxy_call!(
263 disable_unit_files_with_flags,
264 &[unit_file],
265 flags.bits_c() as u64
266 )
267 } else {
268 systemd_manager()
269 .disable_unit_files_with_flags_and_install_info(
270 &[unit_file],
271 flags.bits_c() as u64,
272 )
273 .map_err(|err| err.into())
274 }
275 }
276 UnitDBusLevel::UserSession => sysdbus::dbus_proxies::systemd_manager_session()
277 .disable_unit_files_with_flags_and_install_info(&[unit_file], flags.bits_c() as u64)
278 .map_err(|err| err.into()),
279 }
280
281 #[cfg(feature = "flatpak")]
282 {
283 use crate::sysdbus::dbus_proxies::systemd_manager_blocking;
284 systemd_manager_blocking(level)
285 .disable_unit_files_with_flags_and_install_info(&[unit_file], flags.bits_c() as u64)
286 .map_err(|err| err.into())
287 }
288}
289
290pub async fn fetch_drop_in_paths(
291 level: UnitDBusLevel,
292 unit_name: &str,
293) -> Result<Vec<String>, SystemdErrors> {
294 sysdbus::fetch_drop_in_paths(level, unit_name).await
295}
296pub fn get_unit_file_info(
298 file_path: Option<&str>,
299 unit_primary_name: &str,
300) -> Result<String, SystemdErrors> {
301 let Some(file_path) = file_path else {
302 warn!("No file path for {:?}", unit_primary_name);
303 return Ok(String::new());
304 };
305
306 file_open_get_content(file_path, unit_primary_name)
307}
308
309#[allow(unused)]
310fn flatpak_file_open_get_content(
311 file_path: &str,
312 unit_primary_name: &str,
313) -> Result<String, SystemdErrors> {
314 file_open_get_content(file_path, unit_primary_name).or_else(|e| {
315 info!("Trying to fetch file content through 'cat' command, because {e:?}");
316 file_open_get_content_cat(file_path, unit_primary_name)
317 })
318}
319
320fn file_open_get_content_cat(
321 file_path: &str,
322 unit_primary_name: &str,
323) -> Result<String, SystemdErrors> {
324 info!(
325 "Flatpak Fetching file content Unit: {} File \"{file_path}\"",
326 unit_primary_name
327 );
328 commander_output(&["cat", file_path], None)
330 .map(|cat_output| String::from_utf8_lossy(&cat_output.stdout).to_string())
331 .inspect_err(|e| warn!("Can't open file {file_path:?} with 'cat' command, reason: {e:?}"))
332}
333
334fn file_open_get_content(
335 file_path: &str,
336 unit_primary_name: &str,
337) -> Result<String, SystemdErrors> {
338 let file_path = flatpak_host_file_path(file_path);
340
341 info!(
342 "Fetching file content Unit: {} File: {}",
343 unit_primary_name,
344 file_path.display()
345 );
346
347 let mut file = File::open(&file_path).map_err(|e| {
348 warn!(
349 "Can't open file \"{}\", reason: {e} {:?}",
350 file_path.display(),
351 e.kind()
352 );
353 SystemdErrors::IoError(e)
354 })?;
355
356 let mut output = String::new();
357 let _ = file.read_to_string(&mut output);
358
359 Ok(output)
360}
361
362pub fn get_unit_journal(
364 primary_name: String,
365 level: UnitDBusLevel,
366 boot_filter: BootFilter,
367 range: EventRange,
368 message_max_char: usize,
369 timestamp_style: TimestampStyle,
370) -> Result<JournalEventChunk, SystemdErrors> {
371 journal::get_unit_journal_events(
372 primary_name,
373 level,
374 boot_filter,
375 range,
376 message_max_char,
377 timestamp_style,
378 )
379}
380
381#[allow(clippy::too_many_arguments)]
382pub fn get_unit_journal_continuous(
383 unit_name: String,
384 level: UnitDBusLevel,
385 range: EventRange,
386 journal_continuous_receiver: std::sync::mpsc::Receiver<()>,
387 sender: std::sync::mpsc::Sender<JournalEventChunk>,
388 message_max_char: usize,
389 timestamp_style: TimestampStyle,
390 check_for_new_journal_entry: fn(),
391) {
392 if let Err(err) = journal::get_unit_journal_events_continuous(
393 unit_name,
394 level,
395 range,
396 journal_continuous_receiver,
397 sender,
398 message_max_char,
399 timestamp_style,
400 check_for_new_journal_entry,
401 ) {
402 warn!(
403 "Journal TailError type: {:?} Error: {:?}",
404 err.type_id(),
405 err
406 );
407 } else {
408 warn!("Ok journal tail thread finished");
409 }
410}
411
412pub fn list_boots() -> Result<Vec<Boot>, SystemdErrors> {
413 journal::list_boots()
414}
415
416pub fn fetch_last_time() -> Result<u64, SystemdErrors> {
417 journal::fetch_last_time()
418}
419
420pub fn commander_output(
421 prog_n_args: &[&str],
422 environment_variables: Option<&[(&str, &str)]>,
423) -> Result<std::process::Output, SystemdErrors> {
424 match commander_blocking(prog_n_args, environment_variables).output() {
425 Ok(output) => {
426 if cfg!(feature = "flatpak") {
427 info!("Command Exit status: {}", output.status);
428
429 if !output.status.success() {
430 warn!("Flatpak mode, command line did not succeed, please investigate.");
431 error!("Command exit status: {}", output.status);
432 info!(
433 "{}",
434 String::from_utf8(output.stdout).expect("from_utf8 failed")
435 );
436 error!(
437 "{}",
438 String::from_utf8(output.stderr).expect("from_utf8 failed")
439 );
440 let vec = prog_n_args.iter().map(|s| s.to_string()).collect();
441 return Err(SystemdErrors::CmdNoFreedesktopFlatpakPermission(
442 Some(vec),
443 None,
444 ));
445 }
446 }
447 Ok(output)
448 }
449 Err(err) => {
450 error!("commander_output {err}");
451
452 match test_flatpak_spawn() {
453 Ok(()) => Err(SystemdErrors::IoError(err)),
454 Err(e1) => {
455 error!("commander_output e1 {e1}");
456 Err(SystemdErrors::CmdNoFlatpakSpawn)
457 }
458 }
459 }
460 }
461}
462
463pub fn generate_file_uri(file_path: &str) -> String {
464 let flatpak_host_file_path = flatpak_host_file_path(file_path);
465 format!("file://{}", flatpak_host_file_path.display())
466}
467
468pub fn fetch_system_info() -> Result<BTreeMap<String, String>, SystemdErrors> {
469 sysdbus::fetch_system_info(UnitDBusLevel::System)
471}
472
473pub fn fetch_system_unit_info_native(
474 unit: &UnitInfo,
475) -> Result<HashMap<String, OwnedValue>, SystemdErrors> {
476 let level = unit.dbus_level();
477 let unit_type: UnitType = unit.unit_type();
478
479 let object_path = unit.object_path();
480
481 sysdbus::fetch_system_unit_info_native(level, &object_path, unit_type)
482}
483
484pub fn fetch_unit(
496 level: UnitDBusLevel,
497 unit_primary_name: &str,
498) -> Result<UnitInfo, SystemdErrors> {
499 sysdbus::fetch_unit(level, unit_primary_name)
500}
501
502pub fn kill_unit(
503 level: UnitDBusLevel,
504 primary_name: &str,
505 who: KillWho,
506 signal: i32,
507) -> Result<(), SystemdErrors> {
508 sysdbus::kill_unit(level, primary_name, who, signal)
509}
510
511pub fn freeze_unit(params: Option<(UnitDBusLevel, String)>) -> Result<(), SystemdErrors> {
512 if let Some((_level, primary_name)) = params {
513 #[cfg(not(feature = "flatpak"))]
514 match _level {
515 UnitDBusLevel::System | UnitDBusLevel::Both => {
516 if proxy_switcher::PROXY_SWITCHER.freeze() {
517 proxy_call!(freeze_unit, &primary_name)
518 } else {
519 let proxy = systemd_manager();
520 proxy.freeze_unit(&primary_name)?;
521 Ok(())
522 }
523 }
524 UnitDBusLevel::UserSession => sysdbus::dbus_proxies::systemd_manager_session()
525 .freeze_unit(&primary_name)
526 .map_err(|err| err.into()),
527 }
528
529 #[cfg(feature = "flatpak")]
530 {
531 let proxy = systemd_manager();
532 proxy.freeze_unit(&primary_name)?;
533 Ok(())
534 }
535 } else {
536 Err(SystemdErrors::NoUnit)
537 }
538}
539
540pub fn thaw_unit(params: Option<(UnitDBusLevel, String)>) -> Result<(), SystemdErrors> {
541 let Some((level, primary_name)) = params else {
542 return Err(SystemdErrors::NoUnit);
543 };
544
545 #[cfg(not(feature = "flatpak"))]
546 match level {
547 UnitDBusLevel::System | UnitDBusLevel::Both => {
548 if proxy_switcher::PROXY_SWITCHER.thaw() {
549 proxy_call!(thaw_unit, &primary_name)
550 } else {
551 let proxy = systemd_manager();
552 proxy.thaw_unit(&primary_name)?;
553 Ok(())
554 }
555 }
556 UnitDBusLevel::UserSession => sysdbus::dbus_proxies::systemd_manager_session()
557 .thaw_unit(&primary_name)
558 .map_err(|err| err.into()),
559 }
560
561 #[cfg(feature = "flatpak")]
562 {
563 use crate::sysdbus::dbus_proxies::systemd_manager_blocking;
564 let proxy = systemd_manager_blocking(level);
565 proxy.thaw_unit(&primary_name)?;
566 Ok(())
567 }
568}
569
570pub fn reload_unit(
571 level: UnitDBusLevel,
572 primary_name: &str,
573 mode: StartStopMode,
574) -> Result<String, SystemdErrors> {
575 sysdbus::reload_unit(level, primary_name, mode.as_str())
576}
577
578pub fn queue_signal_unit(
579 level: UnitDBusLevel,
580 primary_name: &str,
581 who: KillWho,
582 signal: i32,
583 value: i32,
584) -> Result<(), SystemdErrors> {
585 sysdbus::queue_signal_unit(level, primary_name, who, signal, value)
586}
587
588pub fn clean_unit(
589 level: UnitDBusLevel,
590 unit_name: &str,
591 what: &[String],
592) -> Result<(), SystemdErrors> {
593 let mut what_peekable = what
595 .iter()
596 .filter(|c_op| *c_op == CleanOption::All.code())
597 .peekable();
598
599 let clean_what: Vec<&str> = if what_peekable.peek().is_some() {
600 vec![CleanOption::All.code()]
601 } else {
602 what.iter().map(|s| s.as_str()).collect()
603 };
604
605 #[cfg(not(feature = "flatpak"))]
606 match level {
607 UnitDBusLevel::System | UnitDBusLevel::Both => {
608 if proxy_switcher::PROXY_SWITCHER.clean() {
609 proxy_call!(clean_unit, unit_name, &clean_what)
610 } else {
611 let proxy = systemd_manager();
612 proxy
613 .clean_unit(unit_name, &clean_what)
614 .map_err(|err| err.into())
615 }
616 }
617 UnitDBusLevel::UserSession => sysdbus::dbus_proxies::systemd_manager_session()
618 .clean_unit(unit_name, &clean_what)
619 .map_err(|err| err.into()),
620 }
621
622 #[cfg(feature = "flatpak")]
623 {
624 use crate::sysdbus::dbus_proxies::systemd_manager_blocking;
625
626 systemd_manager_blocking(level)
627 .clean_unit(unit_name, &clean_what)
628 .map_err(|err| err.into())
629 }
630}
631
632pub fn mask_unit_files(
633 level: UnitDBusLevel,
634 primary_name: &str,
635 runtime: bool,
636 force: bool,
637) -> Result<Vec<DisEnAbleUnitFiles>, SystemdErrors> {
638 sysdbus::mask_unit_files(level, &[primary_name], runtime, force)
639}
640
641pub fn preset_unit_files(
642 level: UnitDBusLevel,
643 primary_name: &str,
644 runtime: bool,
645 force: bool,
646) -> Result<DisEnAbleUnitFilesResponse, SystemdErrors> {
647 sysdbus::preset_unit_file(level, &[primary_name], runtime, force)
648}
649
650pub fn reenable_unit_file(
651 level: UnitDBusLevel,
652 primary_name: &str,
653 runtime: bool,
654 force: bool,
655) -> Result<DisEnAbleUnitFilesResponse, SystemdErrors> {
656 sysdbus::reenable_unit_file(level, &[primary_name], runtime, force)
657}
658
659pub fn unmask_unit_files(
660 level: UnitDBusLevel,
661 primary_name: &str,
662 runtime: bool,
663) -> Result<Vec<DisEnAbleUnitFiles>, SystemdErrors> {
664 sysdbus::unmask_unit_files(level, &[primary_name], runtime)
665}
666
667pub fn link_unit_files(
668 dbus_level: UnitDBusLevel,
669 unit_file: &str,
670 runtime: bool,
671 force: bool,
672) -> Result<Vec<DisEnAbleUnitFiles>, SystemdErrors> {
673 sysdbus::link_unit_files(dbus_level, &[unit_file], runtime, force)
674}
675
676pub async fn daemon_reload(level: UnitDBusLevel) -> Result<(), SystemdErrors> {
677 info!("Reloding Daemon");
678
679 #[cfg(not(feature = "flatpak"))]
680 if level.user_session() || !proxy_switcher::PROXY_SWITCHER.reload() {
681 let proxy = systemd_manager_async(level).await?;
682 proxy.reload().await?;
683 Ok(())
684 } else {
685 proxy_call_async!(reload)
686 }
687
688 #[cfg(feature = "flatpak")]
689 {
690 systemd_manager_async(level)
691 .await?
692 .reload()
693 .await
694 .map_err(|err| err.into())
695 }
696}
697
698#[derive(Debug, PartialEq, Eq)]
699pub struct Dependency {
700 pub unit_name: String,
701 pub state: ActiveState,
702 pub children: BTreeSet<Dependency>,
703}
704
705impl Dependency {
706 pub fn new(unit_name: &str) -> Self {
707 Self {
708 unit_name: unit_name.to_string(),
709 state: ActiveState::Unknown,
710 children: BTreeSet::new(),
711 }
712 }
713
714 fn partial_clone(&self) -> Dependency {
715 Self {
716 unit_name: self.unit_name.clone(),
717 state: self.state,
718 children: BTreeSet::new(),
719 }
720 }
721}
722
723impl Ord for Dependency {
724 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
725 self.unit_name.cmp(&other.unit_name)
726 }
727}
728
729impl PartialOrd for Dependency {
730 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
731 Some(self.cmp(other))
732 }
733}
734
735pub fn fetch_unit_dependencies(
736 level: UnitDBusLevel,
737 primary_name: &str,
738 object_path: &str,
739 dependency_type: DependencyType,
740 plain: bool,
741) -> Result<Dependency, SystemdErrors> {
742 sysdbus::unit_get_dependencies(level, primary_name, object_path, dependency_type, plain)
743}
744
745pub fn get_unit_active_state(
746 level: UnitDBusLevel,
747 primary_name: &str,
748) -> Result<ActiveState, SystemdErrors> {
749 let object_path = sysdbus::unit_dbus_path_from_name(primary_name);
750
751 sysdbus::get_unit_active_state(level, &object_path)
752}
753
754pub fn retreive_unit_processes(
755 unit: &UnitInfo,
756) -> Result<BTreeMap<String, BTreeSet<UnitProcess>>, SystemdErrors> {
757 let level = unit.dbus_level();
758
759 let unit_processes = sysdbus::retreive_unit_processes(level, &unit.primary())?;
760
761 let mut unit_processes_map: BTreeMap<String, BTreeSet<UnitProcess>> = BTreeMap::new();
763 for unit_process in unit_processes {
764 let unit_process = {
765 let Some(unit_name) = unit_process.path.rsplit_once('/').map(|a| a.1) else {
766 warn!("No unit name for path {:?}", unit_process.path);
767 continue;
768 };
769
770 let unit_name_idx = unit_process.path.len() - unit_name.len();
771
772 UnitProcess {
773 path: unit_process.path,
774 pid: unit_process.pid,
775 name: unit_process.name,
776 unit_name: unit_name_idx,
777 }
778 };
779
780 if let Some(set) = unit_processes_map.get_mut(unit_process.unit_name()) {
781 set.insert(unit_process);
782 } else {
783 let mut set = BTreeSet::new();
784 let key = unit_process.unit_name().to_string();
785 set.insert(unit_process);
786 unit_processes_map.insert(key, set);
787 }
788 }
789
790 Ok(unit_processes_map)
791}
792
793#[derive(Debug)]
794pub struct SystemdSignalRow {
795 pub time_stamp: u64,
796 pub signal: SystemdSignal,
797}
798
799impl SystemdSignalRow {
800 pub fn new(signal: SystemdSignal) -> Self {
801 let current_system_time = SystemTime::now();
802 let since_the_epoch = current_system_time
803 .duration_since(UNIX_EPOCH)
804 .expect("Time went backwards");
805 let time_stamp =
806 since_the_epoch.as_secs() * 1_000_000 + since_the_epoch.subsec_nanos() as u64 / 1_000;
807 SystemdSignalRow { time_stamp, signal }
808 }
809
810 pub fn type_text(&self) -> &str {
811 self.signal.type_text()
812 }
813
814 pub fn details(&self) -> String {
815 self.signal.details()
816 }
817}
818
819#[derive(Debug)]
820pub enum SystemdSignal {
821 UnitNew(String, OwnedObjectPath),
822 UnitRemoved(String, OwnedObjectPath),
823 JobNew(u32, OwnedObjectPath, String),
824 JobRemoved(u32, OwnedObjectPath, String, String),
825 StartupFinished(u64, u64, u64, u64, u64, u64),
826 UnitFilesChanged,
827 Reloading(bool),
828}
829
830impl SystemdSignal {
831 pub fn type_text(&self) -> &str {
832 match self {
833 SystemdSignal::UnitNew(_, _) => "UnitNew",
834 SystemdSignal::UnitRemoved(_, _) => "UnitRemoved",
835 SystemdSignal::JobNew(_, _, _) => "JobNew",
836 SystemdSignal::JobRemoved(_, _, _, _) => "JobRemoved",
837 SystemdSignal::StartupFinished(_, _, _, _, _, _) => "StartupFinished",
838 SystemdSignal::UnitFilesChanged => "UnitFilesChanged",
839 SystemdSignal::Reloading(_) => "Reloading",
840 }
841 }
842
843 pub fn details(&self) -> String {
844 match self {
845 SystemdSignal::UnitNew(id, unit) => format!("{id} {unit}"),
846 SystemdSignal::UnitRemoved(id, unit) => format!("{id} {unit}"),
847 SystemdSignal::JobNew(id, job, unit) => {
848 format!("unit={unit} id={id} path={job}")
849 }
850 SystemdSignal::JobRemoved(id, job, unit, result) => {
851 format!("unit={unit} id={id} path={job} result={result}")
852 }
853 SystemdSignal::StartupFinished(firmware, loader, kernel, initrd, userspace, total) => {
854 format!(
855 "firmware={firmware} loader={loader} kernel={kernel} initrd={initrd} userspace={userspace} total={total}",
856 )
857 }
858 SystemdSignal::UnitFilesChanged => String::new(),
859 SystemdSignal::Reloading(active) => format!("firmware={active}"),
860 }
861 }
862}
863
864pub async fn watch_systemd_signals(
865 systemd_signal_sender: mpsc::Sender<SystemdSignalRow>,
866 cancellation_token: tokio_util::sync::CancellationToken,
867) {
868 let result: Result<(), SystemdErrors> =
869 sysdbus::watcher::watch_systemd_signals(systemd_signal_sender, cancellation_token).await;
870
871 if let Err(err) = result {
872 log::error!("Error listening to jobs {err:?}");
873 }
874}
875
876pub async fn test(test_name: &str, level: UnitDBusLevel) {
877 info!("Testing {test_name:?}");
878
879 if let Err(error) = sysdbus::test(test_name, level).await {
880 error!("{error:#?}");
881 }
882}
883
884#[derive(PartialEq, Eq, PartialOrd, Ord, Debug)]
885pub struct UnitPropertyFetch {
886 pub name: String,
887 pub signature: String,
888 pub access: String,
889}
890
891impl UnitPropertyFetch {
892 fn new(p: &zbus_xml::Property) -> Self {
893 let access = match p.access() {
894 zbus_xml::PropertyAccess::Read => "read",
895 zbus_xml::PropertyAccess::Write => "write",
896 zbus_xml::PropertyAccess::ReadWrite => "readwrite",
897 };
898
899 UnitPropertyFetch {
900 name: p.name().to_string(),
901 signature: p.ty().to_string(),
902 access: access.to_string(),
903 }
904 }
905}
906
907pub async fn fetch_unit_interface_properties()
908-> Result<BTreeMap<String, Vec<UnitPropertyFetch>>, SystemdErrors> {
909 sysdbus::fetch_unit_interface_properties().await
910}
911
912pub async fn fetch_unit_properties(
913 level: UnitDBusLevel,
914 path: &str,
915 property_interface: &str,
916 property: &str,
917) -> Result<OwnedValue, SystemdErrors> {
918 sysdbus::fetch_unit_properties(level, path, property_interface, property).await
919}
920
921pub async fn create_drop_in(
922 user_session: bool,
923 runtime: bool,
924 unit_name: &str,
925 file_name: &str,
926 content: &str,
927) -> Result<(), SystemdErrors> {
928 #[cfg(not(feature = "flatpak"))]
929 if user_session || !proxy_switcher::PROXY_SWITCHER.create_dropin() {
930 file::create_drop_in(runtime, user_session, unit_name, file_name, content).await
931 } else {
932 proxy_call_async!(create_drop_in, runtime, unit_name, file_name, content)
933 }
934
935 #[cfg(feature = "flatpak")]
936 {
937 file::create_drop_in(runtime, user_session, unit_name, file_name, content).await
938 }
939}
940
941pub async fn save_file(
942 level: UnitDBusLevel,
943 file_path: &str,
944 content: &str,
945) -> Result<u64, SystemdErrors> {
946 info!("Saving file {file_path:?}");
947
948 let user_session = level.user_session();
949 #[cfg(not(feature = "flatpak"))]
952 if user_session || !proxy_switcher::PROXY_SWITCHER.save_file() {
953 save_text_to_file(file_path, content, user_session).await
954 } else {
955 proxy_call_async!(save_file, file_path, content)
956 }
957
958 #[cfg(feature = "flatpak")]
959 save_text_to_file(file_path, content, user_session).await
960}
961
962pub async fn revert_unit_file_full(
963 level: UnitDBusLevel,
964 unit_name: &str,
965) -> Result<Vec<DisEnAbleUnitFiles>, SystemdErrors> {
966 info!("Reverting unit file {unit_name:?}");
967
968 #[cfg(not(feature = "flatpak"))]
969 if level.user_session() || !proxy_switcher::PROXY_SWITCHER.revert_unit_file() {
970 systemd_manager_async(level)
971 .await?
972 .revert_unit_files(&[unit_name])
973 .await
974 .map_err(|err| err.into())
975 } else {
976 proxy_call_async!(revert_unit_files, &[unit_name])
977 }
978
979 #[cfg(feature = "flatpak")]
980 {
981 systemd_manager_async(level)
982 .await?
983 .revert_unit_files(&[unit_name])
984 .await
985 .map_err(|err| err.into())
986 }
987}