Skip to main content

sysd_manager_comcontroler/
lib.rs

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///Try to Start Proxy
99#[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
118/* pub fn list_units_description_and_state() -> Result<BTreeMap<String, UnitInfo>, SystemdErrors> {
119    let level = match PREFERENCES.dbus_level() {
120        DbusLevel::Session => UnitDBusLevel::UserSession,
121        DbusLevel::System => UnitDBusLevel::System,
122        DbusLevel::SystemAndSession => UnitDBusLevel::System,
123    };
124
125    match sysdbus::list_units_description_and_state(level) {
126        Ok(map) => Ok(map),
127        Err(e) => {
128            warn!("{:?}", e);
129            Err(e)
130        }
131    }
132}
133 */
134
135pub 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
156/// Takes a unit name as input and attempts to start it
157/// # returns
158/// job_path
159pub 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
167/// Takes a unit name as input and attempts to start it
168/// # returns
169/// job_path
170pub 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
178/// Takes a unit name as input and attempts to stop it.
179pub 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}
296/// Read the unit file and return it's contents so that we can display it
297pub 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    //Use the REAL path because try to acceess through the 'cat' command
329    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    //To get the relative path from a Flatpak
339    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
362/// Obtains the journal log for the given unit.
363pub 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    //TODO check with Session (user)
470    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
484/* fn get_unit_path(unit: &UnitInfo) -> String {
485    match unit.object_path() {
486        Some(s) => s,
487        None => {
488            let object_path = sysdbus::unit_dbus_path_from_name(&unit.primary());
489            unit.set_object_path(object_path.clone());
490            object_path
491        }
492    }
493}
494 */
495pub 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    //just send all if seleted
594    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_out = Vec::with_capacity(unit_processes.len());
762    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    //TODO check the case of /run
950
951    #[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}