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