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