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