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(any(feature = "flatpak", feature = "appimage")))]
10pub mod proxy_switcher;
11pub mod socket_unit;
12pub(crate) mod sysdbus;
13pub mod time_handling;
14
15use crate::{
16 data::{ListedLoadedUnit, UnitInfo, UnitProcess, UnitPropertySetter},
17 enums::{
18 ActiveState, CleanOption, DependencyType, DisEnableFlags, KillWho, LoadState,
19 StartStopMode, UnitFileStatus, UnitType,
20 },
21 file::save_text_to_file,
22 journal_data::Boot,
23 sysdbus::{
24 ListedUnitFile,
25 dbus_proxies::{Systemd1ManagerProxy, systemd_manager, systemd_manager_async},
26 watcher::SystemdSignal,
27 },
28 time_handling::TimestampStyle,
29};
30use base::{
31 enums::UnitDBusLevel,
32 file::{
33 commander_blocking, create_drop_in_path_file, flatpak_host_file_path, test_flatpak_spawn,
34 },
35 proxy::{DisEnAbleUnitFiles, DisEnAbleUnitFilesResponse},
36};
37use enumflags2::{BitFlag, BitFlags};
38use errors::SystemdErrors;
39use flagset::{FlagSet, flags};
40use glib::Quark;
41use journal_data::{EventRange, JournalEventChunk};
42use std::{
43 any::Any,
44 collections::{BTreeMap, BTreeSet, HashMap},
45 fs::File,
46 io::Read,
47 sync::OnceLock,
48 time::Duration,
49};
50pub use sysdbus::{
51 get_unit_file_state, list_units_description_and_state_async, sysd_proxy_service_name,
52 watcher::{SystemdSignalRow, init_signal_watcher},
53};
54use tokio::{
55 runtime::Runtime,
56 sync::broadcast::{self, error::RecvError},
57 time::timeout,
58};
59use tracing::{error, info, warn};
60use zvariant::OwnedValue;
61
62#[cfg(not(any(feature = "flatpak", feature = "appimage")))]
63pub use sysdbus::shut_down_sysd_proxy;
64
65#[cfg(not(any(feature = "flatpak", feature = "appimage")))]
66use crate::sysdbus::to_proxy::{self, SysDManagerComLinkProxy};
67
68#[cfg(not(any(feature = "flatpak", feature = "appimage")))]
69use base::consts::PROXY_SERVICE;
70
71#[derive(Default, Clone, PartialEq, Debug)]
72pub enum BootFilter {
73 #[default]
74 Current,
75 All,
76 Id(String),
77}
78
79#[derive(Clone, Debug)]
80pub struct SystemdUnitFile {
82 pub full_name: String,
83 pub status_code: UnitFileStatus,
84 pub level: UnitDBusLevel,
85 pub file_path: String,
86}
87
88#[derive(Debug, Default)]
89pub struct UpdatedUnitInfo {
90 pub primary: String,
91 pub description: Option<String>,
93 pub load_state: Option<LoadState>,
94 pub sub_state: Option<String>,
95 pub active_state: Option<ActiveState>,
96 pub unit_file_preset: Option<String>,
97 pub valid_unit_name: bool,
98 pub fragment_path: Option<String>,
99 pub enablement_status: Option<UnitFileStatus>,
100 pub level: UnitDBusLevel,
101}
102
103impl UpdatedUnitInfo {
104 fn new(primary: String, level: UnitDBusLevel) -> Self {
105 Self {
106 primary,
107 level,
108 ..Default::default()
109 }
110 }
111}
112
113flags! {
114 pub enum UnitPropertiesFlags : u8 {
115 EnablementStatus,
116 ActiveStatus,
117 Description,
118 LoadState,
119 SubState,
120 UnitFilePreset,
121 FragmentPath,
122 }
123}
124
125#[derive(Debug, Clone, Copy)]
126pub struct UnitProperties(pub FlagSet<UnitPropertiesFlags>);
127
128impl UnitProperties {
129 }
134
135pub struct CompleteUnitPropertiesCallParams {
136 pub level: UnitDBusLevel,
137 pub unit_name: String,
138 pub object_path: String,
139 pub status: UnitFileStatus,
140}
141
142impl CompleteUnitPropertiesCallParams {
143 pub fn new(unit: &UnitInfo) -> Self {
144 Self::new_params(
145 unit.dbus_level(),
146 unit.primary(),
147 unit.object_path(),
148 unit.enable_status(),
149 )
150 }
151
152 pub fn new_params(
153 level: UnitDBusLevel,
154 unit_name: String,
155 object_path: String,
156 status: UnitFileStatus,
157 ) -> Self {
158 Self {
159 level,
160 unit_name,
161 object_path,
162 status,
163 }
164 }
165}
166
167pub fn runtime() -> &'static Runtime {
168 static RUNTIME: OnceLock<Runtime> = OnceLock::new();
169 RUNTIME.get_or_init(|| Runtime::new().expect("Setting up tokio runtime needs to succeed."))
170}
171
172#[cfg(not(any(feature = "flatpak", feature = "appimage")))]
174pub async fn init_proxy_async(run_mode: base::RunMode) {
175 if let Err(e) = sysdbus::init_proxy_async(run_mode).await {
176 error!("Fail starting Proxy. Error {e:?}");
177 }
178}
179
180#[cfg(not(any(feature = "flatpak", feature = "appimage")))]
181pub fn shut_down() {
182 sysdbus::shut_down_sysd_proxy();
183}
184
185#[derive(Debug)]
186pub enum ListUnitResponse {
187 Loaded(UnitDBusLevel, Vec<ListedLoadedUnit>),
188 File(UnitDBusLevel, Vec<ListedUnitFile>),
189}
190
191impl ListUnitResponse {
192 pub fn r_len(&self) -> (usize, usize) {
193 match self {
194 ListUnitResponse::Loaded(_, items) => (items.len(), 0),
195 ListUnitResponse::File(_, items) => (0, items.len()),
196 }
197 }
198
199 pub fn t_len(&self) -> usize {
200 match self {
201 ListUnitResponse::Loaded(_, lunits) => lunits.len(),
202 ListUnitResponse::File(_, items) => items.len(),
203 }
204 }
205 pub fn update_flags(&self) -> FlagSet<UnitPropertiesFlags> {
206 match self {
207 ListUnitResponse::Loaded(_, _) => {
208 UnitPropertiesFlags::EnablementStatus
209 | UnitPropertiesFlags::Description
210 | UnitPropertiesFlags::LoadState
211 | UnitPropertiesFlags::SubState
212 | UnitPropertiesFlags::UnitFilePreset
213 }
214
215 ListUnitResponse::File(_, _) => {
216 UnitPropertiesFlags::ActiveStatus
217 | UnitPropertiesFlags::Description
218 | UnitPropertiesFlags::LoadState
219 | UnitPropertiesFlags::SubState
220 | UnitPropertiesFlags::UnitFilePreset
221 }
222 }
223 }
224}
225
226pub async fn list_loaded_units(level: UnitDBusLevel) -> Result<ListUnitResponse, SystemdErrors> {
227 let v = systemd_manager_async(level).await?.list_units().await?;
228 Ok(ListUnitResponse::Loaded(level, v))
229}
230
231pub async fn list_loaded_units_by_patterns(
232 level: UnitDBusLevel,
233 patterns: &[&str],
234) -> Result<ListUnitResponse, SystemdErrors> {
235 let v = systemd_manager_async(level)
236 .await?
237 .list_units_by_patterns(&[], patterns)
238 .await?;
239 Ok(ListUnitResponse::Loaded(level, v))
240}
241
242pub async fn list_loaded_units_timers(
243 level: UnitDBusLevel,
244) -> Result<ListUnitResponse, SystemdErrors> {
245 list_loaded_units_by_patterns(level, &["*.timer"]).await
246}
247
248pub async fn list_loaded_units_sockets(
249 level: UnitDBusLevel,
250) -> Result<ListUnitResponse, SystemdErrors> {
251 list_loaded_units_by_patterns(level, &["*.socket"]).await
252}
253
254pub async fn list_loaded_units_paths(
255 level: UnitDBusLevel,
256) -> Result<ListUnitResponse, SystemdErrors> {
257 list_loaded_units_by_patterns(level, &["*.path"]).await
258}
259
260pub async fn list_loaded_units_automounts(
261 level: UnitDBusLevel,
262) -> Result<ListUnitResponse, SystemdErrors> {
263 list_loaded_units_by_patterns(level, &["*.automount"]).await
264}
265
266pub async fn list_unit_files(level: UnitDBusLevel) -> Result<ListUnitResponse, SystemdErrors> {
267 let v = systemd_manager_async(level)
268 .await?
269 .list_unit_files()
270 .await?;
271 Ok(ListUnitResponse::File(level, v))
272}
273
274pub async fn list_unit_files_by_patterns(
275 level: UnitDBusLevel,
276 patterns: &[&str],
277) -> Result<ListUnitResponse, SystemdErrors> {
278 let v = systemd_manager_async(level)
279 .await?
280 .list_unit_files_by_patterns(&[], patterns)
281 .await?;
282 Ok(ListUnitResponse::File(level, v))
283}
284
285pub async fn list_unit_files_timers(
286 level: UnitDBusLevel,
287) -> Result<ListUnitResponse, SystemdErrors> {
288 list_unit_files_by_patterns(level, &["*.timer"]).await
289}
290
291pub async fn list_unit_files_sockets(
292 level: UnitDBusLevel,
293) -> Result<ListUnitResponse, SystemdErrors> {
294 list_unit_files_by_patterns(level, &["*.socket"]).await
295}
296
297pub async fn list_unit_files_paths(
298 level: UnitDBusLevel,
299) -> Result<ListUnitResponse, SystemdErrors> {
300 list_unit_files_by_patterns(level, &["*.path"]).await
301}
302
303pub async fn list_unit_files_automounts(
304 level: UnitDBusLevel,
305) -> Result<ListUnitResponse, SystemdErrors> {
306 list_unit_files_by_patterns(level, &["*.automount"]).await
307}
308
309pub async fn complete_unit_information(
310 units: &[CompleteUnitPropertiesCallParams],
311) -> Result<Vec<UpdatedUnitInfo>, SystemdErrors> {
312 sysdbus::complete_unit_information(units).await
313}
314
315pub async fn complete_single_unit_information(
316 primary_name: String,
317 level: UnitDBusLevel,
318 object_path: String,
319 status: UnitFileStatus,
320) -> Result<Vec<UpdatedUnitInfo>, SystemdErrors> {
321 let units = [CompleteUnitPropertiesCallParams::new_params(
322 level,
323 primary_name,
324 object_path,
325 status,
326 )];
327 sysdbus::complete_unit_information(&units).await
328}
329
330pub fn start_unit(
334 level: UnitDBusLevel,
335 unit_name: &str,
336 mode: StartStopMode,
337) -> Result<String, SystemdErrors> {
338 runtime()
339 .block_on(async move { restartstop_unit(level, unit_name, mode, ReStartStop::Start).await })
340}
341
342pub fn stop_unit(
344 level: UnitDBusLevel,
345 unit_name: &str,
346 mode: StartStopMode,
347) -> Result<String, SystemdErrors> {
348 runtime()
349 .block_on(async move { restartstop_unit(level, unit_name, mode, ReStartStop::Stop).await })
350}
351
352#[derive(Debug)]
353pub enum ReStartStop {
354 Start,
355 Stop,
356 Restart,
357 ReloadUnit,
358}
359
360impl ReStartStop {
361 #[cfg(not(any(feature = "flatpak", feature = "appimage")))]
362 fn use_proxy(&self) -> bool {
363 use crate::proxy_switcher::PROXY_SWITCHER;
364
365 match self {
366 ReStartStop::Start => PROXY_SWITCHER.start(),
367 ReStartStop::Stop => PROXY_SWITCHER.stop(),
368 ReStartStop::Restart => PROXY_SWITCHER.restart(),
369 ReStartStop::ReloadUnit => PROXY_SWITCHER.reload_unit(),
370 }
371 }
372
373 #[cfg(not(any(feature = "flatpak", feature = "appimage")))]
374 async fn action<'a>(
375 &self,
376 proxy: &SysDManagerComLinkProxy<'a>,
377 unit_name: &str,
378 mode: StartStopMode,
379 ) -> Result<String, SystemdErrors> {
380 let path = match self {
381 ReStartStop::Start => proxy.start_unit(unit_name, mode.as_str()).await?,
382 ReStartStop::Stop => proxy.stop_unit(unit_name, mode.as_str()).await?,
383 ReStartStop::Restart => proxy.restart_unit(unit_name, mode.as_str()).await?,
384 ReStartStop::ReloadUnit => proxy.reload_unit(unit_name, mode.as_str()).await?,
385 };
386
387 Ok(path.to_string())
388 }
389
390 async fn systemd_action<'a>(
391 &self,
392 manager: &Systemd1ManagerProxy<'a>,
393 unit_name: &str,
394 mode: StartStopMode,
395 ) -> Result<String, SystemdErrors> {
396 let path = match self {
397 ReStartStop::Start => manager.start_unit(unit_name, mode.as_str()).await?,
398 ReStartStop::Stop => manager.stop_unit(unit_name, mode.as_str()).await?,
399 ReStartStop::Restart => manager.restart_unit(unit_name, mode.as_str()).await?,
400 ReStartStop::ReloadUnit => manager.reload_unit(unit_name, mode.as_str()).await?,
401 };
402
403 Ok(path.to_string())
404 }
405}
406
407pub async fn restartstop_unit(
408 level: UnitDBusLevel,
409 unit_name: &str,
410 mode: StartStopMode,
411 action: ReStartStop,
412) -> Result<String, SystemdErrors> {
413 let watcher = init_signal_watcher();
414 let job = restartstop_unit_call(level, unit_name, mode, &action).await?;
415 let job_id = job_number(&job).ok_or("Invalid Job Id for job: {job}")?;
416
417 let duration = Duration::from_secs(10);
418 timeout(duration, wait_job_removed(job_id, watcher))
419 .await
420 .map_err(|_err| SystemdErrors::Timeout(duration))
421 .and_then(|res| res.map(|_| job))
422 .inspect(|_job| {
423 #[cfg(not(any(feature = "flatpak", feature = "appimage")))]
424 if matches!(action, ReStartStop::Start | ReStartStop::Restart)
425 && unit_name.starts_with(PROXY_SERVICE)
426 {
427 to_proxy::start_heart_beat()
428 }
429 })
430}
431
432const DONE: &str = "done";
433const SKIPPED: &str = "skipped";
434const CANCELED: &str = "canceled";
435const TIMEOUT: &str = "timeout";
436const FAILED: &str = "failed";
437const DEPENDENCY: &str = "dependency";
438const INVALID: &str = "invalid";
439
440async fn wait_job_removed(
441 job_id: u32,
442 mut watcher: broadcast::Receiver<SystemdSignalRow>,
443) -> Result<(), SystemdErrors> {
444 loop {
445 match watcher.recv().await {
446 Ok(x) => {
447 if let SystemdSignal::JobRemoved(id, _, _unit, result) = x.signal
448 && id == job_id
449 {
450 match result.as_str() {
451 DONE => {
452 break;
453 }
454 CANCELED => return Err(SystemdErrors::JobRemoved(CANCELED.to_owned())),
455 TIMEOUT => return Err(SystemdErrors::JobRemoved(TIMEOUT.to_owned())),
456 FAILED => return Err(SystemdErrors::JobRemoved(FAILED.to_owned())),
457 DEPENDENCY => return Err(SystemdErrors::JobRemoved(DEPENDENCY.to_owned())),
458 SKIPPED => return Err(SystemdErrors::JobRemoved(SKIPPED.to_owned())),
459 INVALID => return Err(SystemdErrors::JobRemoved(INVALID.to_owned())),
460 unkown_result => {
461 warn!("Unknown JobRemoved result {unkown_result}");
462 }
463 }
464 }
465 }
466 Err(RecvError::Lagged(lag)) => info!("Lagged {lag:?}"),
467 Err(err) => {
468 warn!("Recev Err {err:?}");
469 return Err(SystemdErrors::JobRemoved(format!("{err:?}")));
470 }
471 }
472 }
473 Ok(())
474}
475
476fn job_number(job: &str) -> Option<u32> {
477 job.rsplit_once('/').and_then(|(_, job_id)| {
478 job_id
479 .parse::<u32>()
480 .inspect_err(|err| warn!("Job {err:?}"))
481 .ok()
482 })
483}
484
485async fn restartstop_unit_call(
486 level: UnitDBusLevel,
487 unit_name: &str,
488 mode: StartStopMode,
489 action: &ReStartStop,
490) -> Result<String, SystemdErrors> {
491 #[cfg(not(any(feature = "flatpak", feature = "appimage")))]
492 match level {
493 UnitDBusLevel::System | UnitDBusLevel::Both => {
494 use crate::sysdbus::to_proxy::get_proxy_async;
495
496 let proxy = get_proxy_async().await?;
497 if action.use_proxy() && !unit_name.starts_with(PROXY_SERVICE) {
498 match action.action(&proxy, unit_name, mode).await {
501 Ok(ok) => Ok(ok),
502 Err(SystemdErrors::ZFdoServiceUnknowm(msg)) => {
503 warn!("Async ServiceUnkown: {:?} Function: {:?}", msg, action);
504 to_proxy::lazy_start_proxy_async().await;
505 action.action(&proxy, unit_name, mode).await
506 }
507 Err(err) => Err(err),
508 }
509 } else {
510 let manager = sysdbus::dbus_proxies::system_manager_system_async().await?;
511 action.systemd_action(manager, unit_name, mode).await
512 }
513 }
514
515 UnitDBusLevel::UserSession => {
516 let manager = sysdbus::dbus_proxies::system_manager_user_session_async().await?;
517 action.systemd_action(manager, unit_name, mode).await
518 }
519 }
520
521 #[cfg(any(feature = "flatpak", feature = "appimage"))]
522 {
523 let manager = sysdbus::dbus_proxies::systemd_manager_async(level).await?;
524 action.systemd_action(manager, unit_name, mode).await
525 }
526}
527
528pub fn disenable_unit_file(
529 primary_name: &str,
530 level: UnitDBusLevel,
531 enable_status: UnitFileStatus,
532 expected_status: UnitFileStatus,
533) -> Result<DisEnAbleUnitFilesResponse, SystemdErrors> {
534 match expected_status {
535 UnitFileStatus::Enabled | UnitFileStatus::EnabledRuntime => enable_unit_file(
536 level,
537 primary_name,
538 DisEnableFlags::SdSystemdUnitForce.into(),
539 ),
540 _ => {
541 let flags: BitFlags<DisEnableFlags> = if enable_status.is_runtime() {
542 DisEnableFlags::SdSystemdUnitRuntime.into()
543 } else {
544 DisEnableFlags::empty()
545 };
546
547 disable_unit_file(level, primary_name, flags)
548 }
549 }
550}
551
552pub fn enable_unit_file(
553 level: UnitDBusLevel,
554 unit_file: &str,
555 flags: BitFlags<DisEnableFlags>,
556) -> Result<DisEnAbleUnitFilesResponse, SystemdErrors> {
557 #[cfg(not(any(feature = "flatpak", feature = "appimage")))]
558 match level {
559 UnitDBusLevel::System | UnitDBusLevel::Both => {
560 if proxy_switcher::PROXY_SWITCHER.enable_unit_file() {
561 proxy_call_blocking!(
562 enable_unit_files_with_flags,
563 &[unit_file],
564 flags.bits_c() as u64
565 )
566 } else {
567 systemd_manager()
568 .enable_unit_files_with_flags(&[unit_file], flags.bits_c() as u64)
569 .map_err(|err| err.into())
570 }
571 }
572 UnitDBusLevel::UserSession => sysdbus::dbus_proxies::systemd_manager_session()
573 .enable_unit_files_with_flags(&[unit_file], flags.bits_c() as u64)
574 .map_err(|err| err.into()),
575 }
576
577 #[cfg(any(feature = "flatpak", feature = "appimage"))]
578 {
579 use crate::sysdbus::dbus_proxies::systemd_manager_blocking;
580 systemd_manager_blocking(level)
581 .enable_unit_files_with_flags(&[unit_file], flags.bits_c() as u64)
582 .map_err(|err| err.into())
583 }
584}
585
586pub fn disable_unit_file(
587 level: UnitDBusLevel,
588 unit_file: &str,
589 flags: BitFlags<DisEnableFlags>,
590) -> Result<DisEnAbleUnitFilesResponse, SystemdErrors> {
591 info!("{:?} {} {:?}", level, unit_file, flags.bits_c());
592 #[cfg(not(any(feature = "flatpak", feature = "appimage")))]
593 match level {
594 UnitDBusLevel::System | UnitDBusLevel::Both => {
595 if proxy_switcher::PROXY_SWITCHER.disable_unit_file() {
596 proxy_call_blocking!(
597 disable_unit_files_with_flags,
598 &[unit_file],
599 flags.bits_c() as u64
600 )
601 } else {
602 systemd_manager()
603 .disable_unit_files_with_flags_and_install_info(
604 &[unit_file],
605 flags.bits_c() as u64,
606 )
607 .map_err(|err| err.into())
608 }
609 }
610 UnitDBusLevel::UserSession => sysdbus::dbus_proxies::systemd_manager_session()
611 .disable_unit_files_with_flags_and_install_info(&[unit_file], flags.bits_c() as u64)
612 .map_err(|err| err.into()),
613 }
614
615 #[cfg(any(feature = "flatpak", feature = "appimage"))]
616 {
617 use crate::sysdbus::dbus_proxies::systemd_manager_blocking;
618 systemd_manager_blocking(level)
619 .disable_unit_files_with_flags_and_install_info(&[unit_file], flags.bits_c() as u64)
620 .map_err(|err| err.into())
621 }
622}
623
624pub async fn fetch_drop_in_paths(
625 level: UnitDBusLevel,
626 unit_name: &str,
627) -> Result<Vec<String>, SystemdErrors> {
628 sysdbus::fetch_drop_in_paths(level, unit_name).await
629}
630pub fn fetch_unit_file_content(
632 file_path: Option<&str>,
633 unit_primary_name: &str,
634) -> Result<String, SystemdErrors> {
635 let Some(file_path) = file_path else {
636 warn!("No file path for {:?}", unit_primary_name);
637 return Ok(String::new());
638 };
639
640 file_open_get_content(file_path, unit_primary_name)
641}
642
643#[allow(unused)]
644fn flatpak_file_open_get_content(
645 file_path: &str,
646 unit_primary_name: &str,
647) -> Result<String, SystemdErrors> {
648 file_open_get_content(file_path, unit_primary_name).or_else(|e| {
649 info!("Trying to fetch file content through 'cat' command, because {e:?}");
650 file_open_get_content_cat(file_path, unit_primary_name)
651 })
652}
653
654fn file_open_get_content_cat(
655 file_path: &str,
656 unit_primary_name: &str,
657) -> Result<String, SystemdErrors> {
658 info!(
659 "Flatpak Fetching file content Unit: {} File \"{file_path}\"",
660 unit_primary_name
661 );
662 commander_output(&["cat", file_path], None)
664 .map(|cat_output| String::from_utf8_lossy(&cat_output.stdout).to_string())
665 .inspect_err(|e| warn!("Can't open file {file_path:?} with 'cat' command, reason: {e:?}"))
666}
667
668fn file_open_get_content(
669 file_path: &str,
670 unit_primary_name: &str,
671) -> Result<String, SystemdErrors> {
672 let file_path = flatpak_host_file_path(file_path);
674
675 info!(
676 "Fetching file content Unit: {} File: {}",
677 unit_primary_name,
678 file_path.display()
679 );
680
681 let mut file = File::open(&file_path).map_err(|e| {
682 warn!(
683 "Can't open file \"{}\", reason: {e} {:?}",
684 file_path.display(),
685 e.kind()
686 );
687 SystemdErrors::IoError(e)
688 })?;
689
690 let mut output = String::new();
691 let _ = file.read_to_string(&mut output);
692
693 Ok(output)
694}
695
696pub fn get_unit_journal(
698 primary_name: String,
699 level: UnitDBusLevel,
700 boot_filter: BootFilter,
701 range: EventRange,
702 message_max_char: usize,
703 timestamp_style: TimestampStyle,
704) -> Result<JournalEventChunk, SystemdErrors> {
705 journal::get_unit_journal_events(
706 primary_name,
707 level,
708 boot_filter,
709 range,
710 message_max_char,
711 timestamp_style,
712 )
713}
714
715#[allow(clippy::too_many_arguments)]
716pub fn get_unit_journal_continuous(
717 unit_name: String,
718 level: UnitDBusLevel,
719 range: EventRange,
720 journal_continuous_receiver: std::sync::mpsc::Receiver<()>,
721 sender: std::sync::mpsc::Sender<JournalEventChunk>,
722 message_max_char: usize,
723 timestamp_style: TimestampStyle,
724 check_for_new_journal_entry: fn(),
725) {
726 if let Err(err) = journal::get_unit_journal_events_continuous(
727 unit_name,
728 level,
729 range,
730 journal_continuous_receiver,
731 sender,
732 message_max_char,
733 timestamp_style,
734 check_for_new_journal_entry,
735 ) {
736 warn!(
737 "Journal TailError type: {:?} Error: {:?}",
738 err.type_id(),
739 err
740 );
741 } else {
742 warn!("Ok journal tail thread finished");
743 }
744}
745
746pub fn list_boots() -> Result<Vec<Boot>, SystemdErrors> {
747 journal::list_boots()
748}
749
750pub fn fetch_last_time() -> Result<u64, SystemdErrors> {
751 journal::fetch_last_time()
752}
753
754pub fn commander_output(
755 prog_n_args: &[&str],
756 environment_variables: Option<&[(&str, &str)]>,
757) -> Result<std::process::Output, SystemdErrors> {
758 match commander_blocking(prog_n_args, environment_variables).output() {
759 Ok(output) => {
760 if cfg!(feature = "flatpak") {
761 info!("Command Exit status: {}", output.status);
762
763 if !output.status.success() {
764 warn!("Flatpak mode, command line did not succeed, please investigate.");
765 error!("Command exit status: {}", output.status);
766 info!(
767 "{}",
768 String::from_utf8(output.stdout).expect("from_utf8 failed")
769 );
770 error!(
771 "{}",
772 String::from_utf8(output.stderr).expect("from_utf8 failed")
773 );
774 let vec = prog_n_args.iter().map(|s| s.to_string()).collect();
775 return Err(SystemdErrors::CmdNoFreedesktopFlatpakPermission(
776 Some(vec),
777 None,
778 ));
779 }
780 }
781 Ok(output)
782 }
783 Err(err) => {
784 error!("commander_output {err}");
785
786 match test_flatpak_spawn() {
787 Ok(()) => Err(SystemdErrors::IoError(err)),
788 Err(e1) => {
789 error!("commander_output e1 {e1}");
790 Err(SystemdErrors::CmdNoFlatpakSpawn)
791 }
792 }
793 }
794 }
795}
796
797pub fn generate_file_uri(file_path: &str) -> String {
798 let flatpak_host_file_path = flatpak_host_file_path(file_path);
799 format!("file://{}", flatpak_host_file_path.display())
800}
801
802pub fn fetch_system_info() -> Result<Vec<(UnitType, String, String)>, SystemdErrors> {
803 sysdbus::fetch_system_info(UnitDBusLevel::System)
805}
806
807pub fn fetch_system_unit_info_native(
808 unit: &UnitInfo,
809) -> Result<Vec<(UnitType, String, OwnedValue)>, SystemdErrors> {
810 let level = unit.dbus_level();
811 let unit_type: UnitType = unit.unit_type();
812 let object_path = unit.object_path();
813
814 sysdbus::fetch_system_unit_info_native(level, &object_path, unit_type)
815}
816
817pub fn fetch_system_unit_info_native_map(
818 unit: &UnitInfo,
819) -> Result<HashMap<String, OwnedValue>, SystemdErrors> {
820 let level = unit.dbus_level();
821 let unit_type: UnitType = unit.unit_type();
822 let object_path = unit.object_path();
823
824 sysdbus::fetch_system_unit_info_native_map(level, &object_path, unit_type)
825}
826
827pub fn fetch_unit(
839 level: UnitDBusLevel,
840 unit_primary_name: &str,
841) -> Result<UnitInfo, SystemdErrors> {
842 sysdbus::fetch_unit(level, unit_primary_name)
843}
844
845pub fn kill_unit(
846 level: UnitDBusLevel,
847 primary_name: &str,
848 who: KillWho,
849 signal: i32,
850) -> Result<(), SystemdErrors> {
851 sysdbus::kill_unit(level, primary_name, who, signal)
852}
853
854pub fn freeze_unit(params: Option<(UnitDBusLevel, String)>) -> Result<(), SystemdErrors> {
855 if let Some((_level, primary_name)) = params {
856 #[cfg(not(any(feature = "flatpak", feature = "appimage")))]
857 match _level {
858 UnitDBusLevel::System | UnitDBusLevel::Both => {
859 if proxy_switcher::PROXY_SWITCHER.freeze() {
860 proxy_call_blocking!(freeze_unit, &primary_name)
861 } else {
862 let proxy = systemd_manager();
863 proxy.freeze_unit(&primary_name)?;
864 Ok(())
865 }
866 }
867 UnitDBusLevel::UserSession => sysdbus::dbus_proxies::systemd_manager_session()
868 .freeze_unit(&primary_name)
869 .map_err(|err| err.into()),
870 }
871
872 #[cfg(any(feature = "flatpak", feature = "appimage"))]
873 {
874 let proxy = systemd_manager();
875 proxy.freeze_unit(&primary_name)?;
876 Ok(())
877 }
878 } else {
879 Err(SystemdErrors::NoUnit)
880 }
881}
882
883pub fn thaw_unit(params: Option<(UnitDBusLevel, String)>) -> Result<(), SystemdErrors> {
884 let Some((level, primary_name)) = params else {
885 return Err(SystemdErrors::NoUnit);
886 };
887
888 #[cfg(not(any(feature = "flatpak", feature = "appimage")))]
889 match level {
890 UnitDBusLevel::System | UnitDBusLevel::Both => {
891 if proxy_switcher::PROXY_SWITCHER.thaw() {
892 proxy_call_blocking!(thaw_unit, &primary_name)
893 } else {
894 let proxy = systemd_manager();
895 proxy.thaw_unit(&primary_name)?;
896 Ok(())
897 }
898 }
899 UnitDBusLevel::UserSession => sysdbus::dbus_proxies::systemd_manager_session()
900 .thaw_unit(&primary_name)
901 .map_err(|err| err.into()),
902 }
903
904 #[cfg(any(feature = "flatpak", feature = "appimage"))]
905 {
906 use crate::sysdbus::dbus_proxies::systemd_manager_blocking;
907 let proxy = systemd_manager_blocking(level);
908 proxy.thaw_unit(&primary_name)?;
909 Ok(())
910 }
911}
912
913pub fn reload_unit(
914 level: UnitDBusLevel,
915 primary_name: &str,
916 mode: StartStopMode,
917) -> Result<String, SystemdErrors> {
918 sysdbus::reload_unit(level, primary_name, mode.as_str())
919}
920
921pub fn queue_signal_unit(
922 level: UnitDBusLevel,
923 primary_name: &str,
924 who: KillWho,
925 signal: i32,
926 value: i32,
927) -> Result<(), SystemdErrors> {
928 sysdbus::queue_signal_unit(level, primary_name, who, signal, value)
929}
930
931pub fn clean_unit(
932 level: UnitDBusLevel,
933 unit_name: &str,
934 what: &[String],
935) -> Result<(), SystemdErrors> {
936 let mut what_peekable = what
938 .iter()
939 .filter(|c_op| *c_op == CleanOption::All.code())
940 .peekable();
941
942 let clean_what: Vec<&str> = if what_peekable.peek().is_some() {
943 vec![CleanOption::All.code()]
944 } else {
945 what.iter().map(|s| s.as_str()).collect()
946 };
947
948 #[cfg(not(any(feature = "flatpak", feature = "appimage")))]
949 match level {
950 UnitDBusLevel::System | UnitDBusLevel::Both => {
951 if proxy_switcher::PROXY_SWITCHER.clean() {
952 proxy_call_blocking!(clean_unit, unit_name, &clean_what)
953 } else {
954 let proxy = systemd_manager();
955 proxy
956 .clean_unit(unit_name, &clean_what)
957 .map_err(|err| err.into())
958 }
959 }
960 UnitDBusLevel::UserSession => sysdbus::dbus_proxies::systemd_manager_session()
961 .clean_unit(unit_name, &clean_what)
962 .map_err(|err| err.into()),
963 }
964
965 #[cfg(any(feature = "flatpak", feature = "appimage"))]
966 {
967 use crate::sysdbus::dbus_proxies::systemd_manager_blocking;
968
969 systemd_manager_blocking(level)
970 .clean_unit(unit_name, &clean_what)
971 .map_err(|err| err.into())
972 }
973}
974
975pub fn mask_unit_files(
976 level: UnitDBusLevel,
977 primary_name: &str,
978 runtime: bool,
979 force: bool,
980) -> Result<Vec<DisEnAbleUnitFiles>, SystemdErrors> {
981 sysdbus::mask_unit_files(level, &[primary_name], runtime, force)
982}
983
984pub fn preset_unit_files(
985 level: UnitDBusLevel,
986 primary_name: &str,
987 runtime: bool,
988 force: bool,
989) -> Result<DisEnAbleUnitFilesResponse, SystemdErrors> {
990 sysdbus::preset_unit_file(level, &[primary_name], runtime, force)
991}
992
993pub fn reenable_unit_file(
994 level: UnitDBusLevel,
995 primary_name: &str,
996 runtime: bool,
997 force: bool,
998) -> Result<DisEnAbleUnitFilesResponse, SystemdErrors> {
999 sysdbus::reenable_unit_file(level, &[primary_name], runtime, force)
1000}
1001
1002pub fn unmask_unit_files(
1003 level: UnitDBusLevel,
1004 primary_name: &str,
1005 runtime: bool,
1006) -> Result<Vec<DisEnAbleUnitFiles>, SystemdErrors> {
1007 sysdbus::unmask_unit_files(level, &[primary_name], runtime)
1008}
1009
1010pub fn link_unit_files(
1011 dbus_level: UnitDBusLevel,
1012 unit_file: &str,
1013 runtime: bool,
1014 force: bool,
1015) -> Result<Vec<DisEnAbleUnitFiles>, SystemdErrors> {
1016 sysdbus::link_unit_files(dbus_level, &[unit_file], runtime, force)
1017}
1018
1019pub async fn daemon_reload(level: UnitDBusLevel) -> Result<(), SystemdErrors> {
1020 let mut watcher = init_signal_watcher();
1021 daemon_reload_core(level).await?;
1022
1023 let mut wait_reload = async || {
1024 loop {
1025 match watcher.recv().await {
1026 Ok(x) => {
1027 if let SystemdSignal::Reloading(active) = x.signal {
1028 if active {
1029 info!("Reloading!");
1030 } else {
1031 info!("Reload Finised");
1032 break;
1033 }
1034 }
1035 }
1036 Err(RecvError::Lagged(lag)) => info!("Lagged {lag:?}"),
1037 Err(err) => {
1038 warn!("Recev Err {err:?}");
1039 break;
1040 }
1041 }
1042 }
1043 };
1044
1045 let duration = Duration::from_secs(10);
1046 match timeout(duration, wait_reload()).await {
1047 Ok(_) => Ok(()),
1048 Err(_err) => Err(SystemdErrors::Timeout(duration)),
1049 }
1050}
1051
1052async fn daemon_reload_core(level: UnitDBusLevel) -> Result<(), SystemdErrors> {
1053 #[cfg(not(any(feature = "flatpak", feature = "appimage")))]
1054 if level.user_session() || !proxy_switcher::PROXY_SWITCHER.reload() {
1055 info!("Reloading Daemon - Direct");
1056 systemd_manager_async(level)
1057 .await?
1058 .reload()
1059 .await
1060 .map_err(|err| err.into())
1061 } else {
1062 info!("Reloading Daemon - Proxy");
1063 proxy_call_async!(reload)
1064 }
1065
1066 #[cfg(any(feature = "flatpak", feature = "appimage"))]
1067 {
1068 info!("Reloading Daemon - Direct");
1069 systemd_manager_async(level)
1070 .await?
1071 .reload()
1072 .await
1073 .map_err(|err| err.into())
1074 }
1075}
1076
1077#[derive(Debug, PartialEq, Eq)]
1078pub struct Dependency {
1079 pub unit_name: String,
1080 pub state: ActiveState,
1081 pub children: BTreeSet<Dependency>,
1082}
1083
1084impl Dependency {
1085 pub fn new(unit_name: &str) -> Self {
1086 Self {
1087 unit_name: unit_name.to_string(),
1088 state: ActiveState::Unknown,
1089 children: BTreeSet::new(),
1090 }
1091 }
1092
1093 fn partial_clone(&self) -> Dependency {
1094 Self {
1095 unit_name: self.unit_name.clone(),
1096 state: self.state,
1097 children: BTreeSet::new(),
1098 }
1099 }
1100}
1101
1102impl Ord for Dependency {
1103 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
1104 self.unit_name.cmp(&other.unit_name)
1105 }
1106}
1107
1108impl PartialOrd for Dependency {
1109 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
1110 Some(self.cmp(other))
1111 }
1112}
1113
1114pub fn fetch_unit_dependencies(
1115 level: UnitDBusLevel,
1116 primary_name: &str,
1117 object_path: &str,
1118 dependency_type: DependencyType,
1119 plain: bool,
1120) -> Result<Dependency, SystemdErrors> {
1121 sysdbus::unit_get_dependencies(level, primary_name, object_path, dependency_type, plain)
1122}
1123
1124pub fn get_unit_active_state(
1125 level: UnitDBusLevel,
1126 primary_name: &str,
1127) -> Result<ActiveState, SystemdErrors> {
1128 let object_path = sysdbus::unit_dbus_path_from_name(primary_name);
1129
1130 sysdbus::get_unit_active_state(level, &object_path)
1131}
1132
1133pub fn retreive_unit_processes(
1134 unit: &UnitInfo,
1135) -> Result<BTreeMap<String, BTreeSet<UnitProcess>>, SystemdErrors> {
1136 let level = unit.dbus_level();
1137
1138 let unit_processes = sysdbus::retreive_unit_processes(level, &unit.primary())?;
1139
1140 let mut unit_processes_map: BTreeMap<String, BTreeSet<UnitProcess>> = BTreeMap::new();
1142 for unit_process in unit_processes {
1143 let unit_process = {
1144 let Some(unit_name) = unit_process.path.rsplit_once('/').map(|a| a.1) else {
1145 warn!("No unit name for path {:?}", unit_process.path);
1146 continue;
1147 };
1148
1149 let unit_name_idx = unit_process.path.len() - unit_name.len();
1150
1151 UnitProcess {
1152 path: unit_process.path,
1153 pid: unit_process.pid,
1154 name: unit_process.name,
1155 unit_name: unit_name_idx,
1156 }
1157 };
1158
1159 if let Some(set) = unit_processes_map.get_mut(unit_process.unit_name()) {
1160 set.insert(unit_process);
1161 } else {
1162 let mut set = BTreeSet::new();
1163 let key = unit_process.unit_name().to_string();
1164 set.insert(unit_process);
1165 unit_processes_map.insert(key, set);
1166 }
1167 }
1168
1169 Ok(unit_processes_map)
1170}
1171
1172pub async fn test(test_name: &str, level: UnitDBusLevel) {
1173 info!("Testing {test_name:?}");
1174
1175 if let Err(error) = sysdbus::test(test_name, level).await {
1176 error!("{error:#?}");
1177 }
1178}
1179
1180#[derive(PartialEq, Eq, PartialOrd, Ord, Debug)]
1181pub struct UnitPropertyFetch {
1182 pub name: String,
1183 pub signature: String,
1184 pub access: String,
1185}
1186
1187impl UnitPropertyFetch {
1188 fn new(p: &zbus_xml::Property) -> Self {
1189 let access = match p.access() {
1190 zbus_xml::PropertyAccess::Read => "read",
1191 zbus_xml::PropertyAccess::Write => "write",
1192 zbus_xml::PropertyAccess::ReadWrite => "readwrite",
1193 };
1194
1195 UnitPropertyFetch {
1196 name: p.name().to_string(),
1197 signature: p.ty().to_string(),
1198 access: access.to_string(),
1199 }
1200 }
1201}
1202
1203pub async fn fetch_unit_interface_properties()
1204-> Result<BTreeMap<String, Vec<UnitPropertyFetch>>, SystemdErrors> {
1205 sysdbus::fetch_unit_interface_properties().await
1206}
1207
1208pub async fn fetch_unit_properties(
1209 level: UnitDBusLevel,
1210 unit_primary_name: &str,
1211 path: &str,
1212 unit_properties: UnitProperties,
1213 properties: Vec<(UnitType, &str, Quark)>,
1214) -> Result<Vec<UnitPropertySetter>, SystemdErrors> {
1215 sysdbus::fetch_unit_properties(level, unit_primary_name, path, unit_properties, properties)
1216 .await
1217}
1218
1219pub fn fetch_unit_property_blocking(
1220 level: UnitDBusLevel,
1221 unit_primary_name: &str,
1222 unit_type: UnitType,
1223 unit_property: &str,
1224) -> Result<OwnedValue, SystemdErrors> {
1225 sysdbus::fetch_unit_property_blocking(level, unit_primary_name, unit_type, unit_property)
1226}
1227
1228pub async fn create_drop_in(
1229 user_session: bool,
1230 runtime: bool,
1231 unit_name: &str,
1232 file_name: &str,
1233 content: &str,
1234) -> Result<String, SystemdErrors> {
1235 let file_path = create_drop_in_path_file(unit_name, runtime, user_session, file_name)?;
1236
1237 #[cfg(not(any(feature = "flatpak", feature = "appimage")))]
1238 let result = if user_session || !proxy_switcher::PROXY_SWITCHER.create_dropin() {
1239 file::create_drop_in(user_session, &file_path, content).await
1240 } else {
1241 proxy_call_async!(create_drop_in, runtime, unit_name, &file_path, content)
1242 };
1243
1244 #[cfg(any(feature = "flatpak", feature = "appimage"))]
1245 let result = file::create_drop_in(user_session, &file_path, content).await;
1246
1247 result.map(|_| file_path)
1248}
1249
1250pub async fn save_file(
1251 level: UnitDBusLevel,
1252 file_path: &str,
1253 content: &str,
1254) -> Result<u64, SystemdErrors> {
1255 info!("Saving file {file_path:?}");
1256
1257 let user_session = level.user_session();
1258 #[cfg(not(any(feature = "flatpak", feature = "appimage")))]
1261 if user_session || !proxy_switcher::PROXY_SWITCHER.save_file() {
1262 save_text_to_file(file_path, content, user_session).await
1263 } else {
1264 proxy_call_async!(save_file, file_path, content)
1265 }
1266
1267 #[cfg(any(feature = "flatpak", feature = "appimage"))]
1268 save_text_to_file(file_path, content, user_session).await
1269}
1270
1271pub async fn revert_unit_file_full(
1272 level: UnitDBusLevel,
1273 unit_name: &str,
1274) -> Result<Vec<DisEnAbleUnitFiles>, SystemdErrors> {
1275 info!("Reverting unit file {unit_name:?}");
1276
1277 #[cfg(not(any(feature = "flatpak", feature = "appimage")))]
1278 if level.user_session() || !proxy_switcher::PROXY_SWITCHER.revert_unit_file() {
1279 systemd_manager_async(level)
1280 .await?
1281 .revert_unit_files(&[unit_name])
1282 .await
1283 .map_err(|err| err.into())
1284 } else {
1285 proxy_call_async!(revert_unit_files, &[unit_name])
1286 }
1287
1288 #[cfg(any(feature = "flatpak", feature = "appimage"))]
1289 {
1290 systemd_manager_async(level)
1291 .await?
1292 .revert_unit_files(&[unit_name])
1293 .await
1294 .map_err(|err| err.into())
1295 }
1296}
1297pub async fn fill_list_unit_files(
1298 level: UnitDBusLevel,
1299) -> Result<Vec<SystemdUnitFile>, SystemdErrors> {
1300 sysdbus::fill_list_unit_files(level).await
1301}