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