sysd_manager_comcontroler/
lib.rs

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