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}
308
309pub async fn list_unit_files_paths(
310    level: UnitDBusLevel,
311) -> Result<ListUnitResponse, SystemdErrors> {
312    list_unit_files_by_patterns(level, &["*.path"]).await
313}
314
315pub async fn list_unit_files_automounts(
316    level: UnitDBusLevel,
317) -> Result<ListUnitResponse, SystemdErrors> {
318    list_unit_files_by_patterns(level, &["*.automount"]).await
319}
320
321pub async fn list_unit_files_list(
322    level: UnitDBusLevel,
323    unit_list: Vec<String>,
324) -> Result<ListUnitResponse, SystemdErrors> {
325    let unit_list: Vec<_> = unit_list.iter().map(|s| s.as_ref()).collect();
326    list_unit_files_by_patterns(level, &unit_list).await
327}
328
329pub async fn complete_unit_information(
330    units: &[CompleteUnitPropertiesCallParams],
331) -> Result<Vec<UpdatedUnitInfo>, SystemdErrors> {
332    sysdbus::complete_unit_information(units).await
333}
334
335pub async fn complete_single_unit_information(
336    primary_name: String,
337    level: UnitDBusLevel,
338    object_path: String,
339    status: UnitFileStatus,
340) -> Result<Vec<UpdatedUnitInfo>, SystemdErrors> {
341    let units = [CompleteUnitPropertiesCallParams::new_params(
342        level,
343        primary_name,
344        object_path,
345        status,
346    )];
347    sysdbus::complete_unit_information(&units).await
348}
349
350/// Takes a unit name as input and attempts to start it
351/// # returns
352/// job_path
353pub fn start_unit(
354    level: UnitDBusLevel,
355    unit_name: &str,
356    mode: StartStopMode,
357) -> Result<String, SystemdErrors> {
358    runtime()
359        .block_on(async move { restartstop_unit(level, unit_name, mode, ReStartStop::Start).await })
360}
361
362/// Takes a unit name as input and attempts to stop it.
363pub fn stop_unit(
364    level: UnitDBusLevel,
365    unit_name: &str,
366    mode: StartStopMode,
367) -> Result<String, SystemdErrors> {
368    runtime()
369        .block_on(async move { restartstop_unit(level, unit_name, mode, ReStartStop::Stop).await })
370}
371
372#[derive(Debug)]
373pub enum ReStartStop {
374    Start,
375    Stop,
376    Restart,
377    ReloadUnit,
378}
379
380impl ReStartStop {
381    #[cfg(not(any(feature = "flatpak", feature = "appimage")))]
382    fn use_proxy(&self) -> bool {
383        use crate::proxy_switcher::PROXY_SWITCHER;
384
385        match self {
386            ReStartStop::Start => PROXY_SWITCHER.start(),
387            ReStartStop::Stop => PROXY_SWITCHER.stop(),
388            ReStartStop::Restart => PROXY_SWITCHER.restart(),
389            ReStartStop::ReloadUnit => PROXY_SWITCHER.reload_unit(),
390        }
391    }
392
393    #[cfg(not(any(feature = "flatpak", feature = "appimage")))]
394    async fn action<'a>(
395        &self,
396        proxy: &SysDManagerComLinkProxy<'a>,
397        unit_name: &str,
398        mode: StartStopMode,
399    ) -> Result<String, SystemdErrors> {
400        let path = match self {
401            ReStartStop::Start => proxy.start_unit(unit_name, mode.as_str()).await?,
402            ReStartStop::Stop => proxy.stop_unit(unit_name, mode.as_str()).await?,
403            ReStartStop::Restart => proxy.restart_unit(unit_name, mode.as_str()).await?,
404            ReStartStop::ReloadUnit => proxy.reload_unit(unit_name, mode.as_str()).await?,
405        };
406
407        Ok(path.to_string())
408    }
409
410    async fn systemd_action<'a>(
411        &self,
412        manager: &Systemd1ManagerProxy<'a>,
413        unit_name: &str,
414        mode: StartStopMode,
415    ) -> Result<String, SystemdErrors> {
416        let path = match self {
417            ReStartStop::Start => manager.start_unit(unit_name, mode.as_str()).await?,
418            ReStartStop::Stop => manager.stop_unit(unit_name, mode.as_str()).await?,
419            ReStartStop::Restart => manager.restart_unit(unit_name, mode.as_str()).await?,
420            ReStartStop::ReloadUnit => manager.reload_unit(unit_name, mode.as_str()).await?,
421        };
422
423        Ok(path.to_string())
424    }
425}
426
427pub async fn restartstop_unit(
428    level: UnitDBusLevel,
429    unit_name: &str,
430    mode: StartStopMode,
431    action: ReStartStop,
432) -> Result<String, SystemdErrors> {
433    let watcher = init_signal_watcher(level).await;
434    let job = restartstop_unit_call(level, unit_name, mode, &action).await?;
435    let job_id = job_number(&job).ok_or("Invalid Job Id for job: {job}")?;
436
437    let duration = Duration::from_secs(10);
438    timeout(duration, wait_job_removed(job_id, watcher))
439        .await
440        .map_err(|_err| SystemdErrors::Timeout(duration))
441        .and_then(|res| res.map(|_| job))
442        .inspect(|_job| {
443            #[cfg(not(any(feature = "flatpak", feature = "appimage")))]
444            if matches!(action, ReStartStop::Start | ReStartStop::Restart)
445                && unit_name.starts_with(PROXY_SERVICE)
446            {
447                to_proxy::start_heart_beat()
448            }
449        })
450}
451
452const DONE: &str = "done";
453const SKIPPED: &str = "skipped";
454const CANCELED: &str = "canceled";
455const TIMEOUT: &str = "timeout";
456const FAILED: &str = "failed";
457const DEPENDENCY: &str = "dependency";
458const INVALID: &str = "invalid";
459
460async fn wait_job_removed(
461    job_id: u32,
462    mut watcher: broadcast::Receiver<SystemdSignal>,
463) -> Result<(), SystemdErrors> {
464    loop {
465        match watcher.recv().await {
466            Ok(SystemdSignal::JobRemoved(_level, id, _, _unit, result)) if id == job_id => {
467                match result.as_str() {
468                    DONE => {
469                        break;
470                    }
471                    CANCELED => return Err(SystemdErrors::JobRemoved(CANCELED.to_owned())),
472                    TIMEOUT => return Err(SystemdErrors::JobRemoved(TIMEOUT.to_owned())),
473                    FAILED => return Err(SystemdErrors::JobRemoved(FAILED.to_owned())),
474                    DEPENDENCY => return Err(SystemdErrors::JobRemoved(DEPENDENCY.to_owned())),
475                    SKIPPED => return Err(SystemdErrors::JobRemoved(SKIPPED.to_owned())),
476                    INVALID => return Err(SystemdErrors::JobRemoved(INVALID.to_owned())),
477                    unkown_result => {
478                        warn!("Unknown JobRemoved result {unkown_result}");
479                    }
480                }
481            }
482            Ok(_) => {}
483            Err(RecvError::Lagged(lag)) => info!("Lagged {lag:?}"),
484            Err(err) => {
485                warn!("Recev Err {err:?}");
486                return Err(SystemdErrors::JobRemoved(format!("{err:?}")));
487            }
488        }
489    }
490    Ok(())
491}
492
493fn job_number(job: &str) -> Option<u32> {
494    job.rsplit_once('/').and_then(|(_, job_id)| {
495        job_id
496            .parse::<u32>()
497            .inspect_err(|err| warn!("Job {err:?}"))
498            .ok()
499    })
500}
501
502async fn restartstop_unit_call(
503    level: UnitDBusLevel,
504    unit_name: &str,
505    mode: StartStopMode,
506    action: &ReStartStop,
507) -> Result<String, SystemdErrors> {
508    #[cfg(not(any(feature = "flatpak", feature = "appimage")))]
509    match level {
510        UnitDBusLevel::System | UnitDBusLevel::Both => {
511            use crate::sysdbus::to_proxy::get_proxy_async;
512
513            let proxy = get_proxy_async().await?;
514            if action.use_proxy() && !unit_name.starts_with(PROXY_SERVICE) {
515                // proxy_call_blocking!(restart_unit, unit_name, mode.as_str())
516
517                match action.action(&proxy, unit_name, mode).await {
518                    Ok(ok) => Ok(ok),
519                    Err(SystemdErrors::ZFdoServiceUnknowm(msg)) => {
520                        warn!("Async ServiceUnkown: {:?} Function: {:?}", msg, action);
521                        to_proxy::lazy_start_proxy_async().await;
522                        action.action(&proxy, unit_name, mode).await
523                    }
524                    Err(err) => Err(err),
525                }
526            } else {
527                let manager = sysdbus::dbus_proxies::system_manager_system_async().await?;
528                action.systemd_action(manager, unit_name, mode).await
529            }
530        }
531
532        UnitDBusLevel::UserSession => {
533            let manager = sysdbus::dbus_proxies::system_manager_user_session_async().await?;
534            action.systemd_action(manager, unit_name, mode).await
535        }
536    }
537
538    #[cfg(any(feature = "flatpak", feature = "appimage"))]
539    {
540        let manager = sysdbus::dbus_proxies::systemd_manager_async(level).await?;
541        action.systemd_action(manager, unit_name, mode).await
542    }
543}
544
545pub fn disenable_unit_file(
546    primary_name: &str,
547    level: UnitDBusLevel,
548    enable_status: UnitFileStatus,
549    expected_status: UnitFileStatus,
550) -> Result<DisEnAbleUnitFilesResponse, SystemdErrors> {
551    match expected_status {
552        UnitFileStatus::Enabled | UnitFileStatus::EnabledRuntime => enable_unit_file(
553            level,
554            primary_name,
555            DisEnableFlags::SdSystemdUnitForce.into(),
556        ),
557        _ => {
558            let flags: BitFlags<DisEnableFlags> = if enable_status.is_runtime() {
559                DisEnableFlags::SdSystemdUnitRuntime.into()
560            } else {
561                DisEnableFlags::empty()
562            };
563
564            disable_unit_file(level, primary_name, flags)
565        }
566    }
567}
568
569pub fn enable_unit_file(
570    level: UnitDBusLevel,
571    unit_file: &str,
572    flags: BitFlags<DisEnableFlags>,
573) -> Result<DisEnAbleUnitFilesResponse, SystemdErrors> {
574    #[cfg(not(any(feature = "flatpak", feature = "appimage")))]
575    match level {
576        UnitDBusLevel::System | UnitDBusLevel::Both => {
577            if proxy_switcher::PROXY_SWITCHER.enable_unit_file() {
578                proxy_call_blocking!(
579                    enable_unit_files_with_flags,
580                    &[unit_file],
581                    flags.bits_c() as u64
582                )
583            } else {
584                systemd_manager()
585                    .enable_unit_files_with_flags(&[unit_file], flags.bits_c() as u64)
586                    .map_err(|err| err.into())
587            }
588        }
589        UnitDBusLevel::UserSession => sysdbus::dbus_proxies::systemd_manager_session()
590            .enable_unit_files_with_flags(&[unit_file], flags.bits_c() as u64)
591            .map_err(|err| err.into()),
592    }
593
594    #[cfg(any(feature = "flatpak", feature = "appimage"))]
595    {
596        use crate::sysdbus::dbus_proxies::systemd_manager_blocking;
597        systemd_manager_blocking(level)
598            .enable_unit_files_with_flags(&[unit_file], flags.bits_c() as u64)
599            .map_err(|err| err.into())
600    }
601}
602
603pub fn disable_unit_file(
604    level: UnitDBusLevel,
605    unit_file: &str,
606    flags: BitFlags<DisEnableFlags>,
607) -> Result<DisEnAbleUnitFilesResponse, SystemdErrors> {
608    info!("{:?} {} {:?}", level, unit_file, flags.bits_c());
609    #[cfg(not(any(feature = "flatpak", feature = "appimage")))]
610    match level {
611        UnitDBusLevel::System | UnitDBusLevel::Both => {
612            if proxy_switcher::PROXY_SWITCHER.disable_unit_file() {
613                proxy_call_blocking!(
614                    disable_unit_files_with_flags,
615                    &[unit_file],
616                    flags.bits_c() as u64
617                )
618            } else {
619                systemd_manager()
620                    .disable_unit_files_with_flags_and_install_info(
621                        &[unit_file],
622                        flags.bits_c() as u64,
623                    )
624                    .map_err(|err| err.into())
625            }
626        }
627        UnitDBusLevel::UserSession => sysdbus::dbus_proxies::systemd_manager_session()
628            .disable_unit_files_with_flags_and_install_info(&[unit_file], flags.bits_c() as u64)
629            .map_err(|err| err.into()),
630    }
631
632    #[cfg(any(feature = "flatpak", feature = "appimage"))]
633    {
634        use crate::sysdbus::dbus_proxies::systemd_manager_blocking;
635        systemd_manager_blocking(level)
636            .disable_unit_files_with_flags_and_install_info(&[unit_file], flags.bits_c() as u64)
637            .map_err(|err| err.into())
638    }
639}
640
641pub async fn fetch_drop_in_paths(
642    level: UnitDBusLevel,
643    unit_name: &str,
644) -> Result<Vec<String>, SystemdErrors> {
645    sysdbus::fetch_drop_in_paths(level, unit_name).await
646}
647/// Read the unit file and return it's contents so that we can display it
648pub fn fetch_unit_file_content(
649    file_path: Option<&str>,
650    unit_primary_name: &str,
651) -> Result<String, SystemdErrors> {
652    let Some(file_path) = file_path else {
653        warn!("No file path for {:?}", unit_primary_name);
654        return Ok(String::new());
655    };
656
657    file_open_get_content(file_path, unit_primary_name)
658}
659
660#[allow(unused)]
661fn flatpak_file_open_get_content(
662    file_path: &str,
663    unit_primary_name: &str,
664) -> Result<String, SystemdErrors> {
665    file_open_get_content(file_path, unit_primary_name).or_else(|e| {
666        info!("Trying to fetch file content through 'cat' command, because {e:?}");
667        file_open_get_content_cat(file_path, unit_primary_name)
668    })
669}
670
671fn file_open_get_content_cat(
672    file_path: &str,
673    unit_primary_name: &str,
674) -> Result<String, SystemdErrors> {
675    info!(
676        "Flatpak Fetching file content Unit: {} File \"{file_path}\"",
677        unit_primary_name
678    );
679    //Use the REAL path because try to acceess through the 'cat' command
680    commander_output(&["cat", file_path], None)
681        .map(|cat_output| String::from_utf8_lossy(&cat_output.stdout).to_string())
682        .inspect_err(|e| warn!("Can't open file {file_path:?} with 'cat' command, reason: {e:?}"))
683}
684
685fn file_open_get_content(
686    file_path: &str,
687    unit_primary_name: &str,
688) -> Result<String, SystemdErrors> {
689    //To get the relative path from a Flatpak
690    let file_path = flatpak_host_file_path(file_path);
691
692    info!(
693        "Fetching file content Unit: {} File: {}",
694        unit_primary_name,
695        file_path.display()
696    );
697
698    let mut file = File::open(&file_path).map_err(|e| {
699        warn!(
700            "Can't open file \"{}\", reason: {e} {:?}",
701            file_path.display(),
702            e.kind()
703        );
704        SystemdErrors::IoError(e)
705    })?;
706
707    let mut output = String::new();
708    let _ = file.read_to_string(&mut output);
709
710    Ok(output)
711}
712
713/// Obtains the journal log for the given unit.
714pub fn get_unit_journal(
715    primary_name: String,
716    level: UnitDBusLevel,
717    boot_filter: BootFilter,
718    range: EventRange,
719    message_max_char: usize,
720    timestamp_style: TimestampStyle,
721) -> Result<JournalEventChunk, SystemdErrors> {
722    journal::get_unit_journal_events(
723        primary_name,
724        level,
725        boot_filter,
726        range,
727        message_max_char,
728        timestamp_style,
729    )
730}
731
732#[allow(clippy::too_many_arguments)]
733pub fn get_unit_journal_continuous(
734    unit_name: String,
735    level: UnitDBusLevel,
736    range: EventRange,
737    journal_continuous_receiver: std::sync::mpsc::Receiver<()>,
738    sender: std::sync::mpsc::Sender<JournalEventChunk>,
739    message_max_char: usize,
740    timestamp_style: TimestampStyle,
741    check_for_new_journal_entry: fn(),
742) {
743    if let Err(err) = journal::get_unit_journal_events_continuous(
744        unit_name,
745        level,
746        range,
747        journal_continuous_receiver,
748        sender,
749        message_max_char,
750        timestamp_style,
751        check_for_new_journal_entry,
752    ) {
753        warn!(
754            "Journal TailError type: {:?}  Error: {:?}",
755            err.type_id(),
756            err
757        );
758    } else {
759        warn!("Ok journal tail thread finished");
760    }
761}
762
763pub fn list_boots() -> Result<Vec<Boot>, SystemdErrors> {
764    journal::list_boots()
765}
766
767pub fn fetch_last_time() -> Result<u64, SystemdErrors> {
768    journal::fetch_last_time()
769}
770
771pub fn commander_output(
772    prog_n_args: &[&str],
773    environment_variables: Option<&[(&str, &str)]>,
774) -> Result<std::process::Output, SystemdErrors> {
775    match commander_blocking(prog_n_args, environment_variables).output() {
776        Ok(output) => {
777            if cfg!(feature = "flatpak") {
778                info!("Command Exit status: {}", output.status);
779
780                if !output.status.success() {
781                    warn!("Flatpak mode, command line did not succeed, please investigate.");
782                    error!("Command exit status: {}", output.status);
783                    info!(
784                        "{}",
785                        String::from_utf8(output.stdout).expect("from_utf8 failed")
786                    );
787                    error!(
788                        "{}",
789                        String::from_utf8(output.stderr).expect("from_utf8 failed")
790                    );
791                    let vec = prog_n_args.iter().map(|s| s.to_string()).collect();
792                    return Err(SystemdErrors::CmdNoFreedesktopFlatpakPermission(
793                        Some(vec),
794                        None,
795                    ));
796                }
797            }
798            Ok(output)
799        }
800        Err(err) => {
801            error!("commander_output {err}");
802
803            match test_flatpak_spawn() {
804                Ok(()) => Err(SystemdErrors::IoError(err)),
805                Err(e1) => {
806                    error!("commander_output e1 {e1}");
807                    Err(SystemdErrors::CmdNoFlatpakSpawn)
808                }
809            }
810        }
811    }
812}
813
814pub fn generate_file_uri(file_path: &str) -> String {
815    let flatpak_host_file_path = flatpak_host_file_path(file_path);
816    format!("file://{}", flatpak_host_file_path.display())
817}
818
819pub fn fetch_system_info() -> Result<Vec<(UnitType, String, String)>, SystemdErrors> {
820    //TODO check with Session (user)
821    sysdbus::fetch_system_info(UnitDBusLevel::System)
822}
823
824pub fn fetch_system_unit_info_native(
825    unit: &UnitInfo,
826) -> Result<Vec<(UnitType, String, OwnedValue)>, SystemdErrors> {
827    let level = unit.dbus_level();
828    let unit_type: UnitType = unit.unit_type();
829    let object_path = unit.object_path();
830
831    sysdbus::fetch_system_unit_info_native(level, &object_path, unit_type)
832}
833
834pub fn fetch_system_unit_info_native_map(
835    unit: &UnitInfo,
836) -> Result<HashMap<String, OwnedValue>, SystemdErrors> {
837    let level = unit.dbus_level();
838    let unit_type: UnitType = unit.unit_type();
839    let object_path = unit.object_path();
840
841    sysdbus::fetch_system_unit_info_native_map(level, &object_path, unit_type)
842}
843
844/* fn get_unit_path(unit: &UnitInfo) -> String {
845    match unit.object_path() {
846        Some(s) => s,
847        None => {
848            let object_path = sysdbus::unit_dbus_path_from_name(&unit.primary());
849            unit.set_object_path(object_path.clone());
850            object_path
851        }
852    }
853}
854 */
855pub fn fetch_unit(
856    level: UnitDBusLevel,
857    unit_primary_name: &str,
858) -> Result<UnitInfo, SystemdErrors> {
859    sysdbus::fetch_unit(level, unit_primary_name)
860}
861
862pub fn kill_unit(
863    level: UnitDBusLevel,
864    primary_name: &str,
865    who: KillWho,
866    signal: i32,
867) -> Result<(), SystemdErrors> {
868    sysdbus::kill_unit(level, primary_name, who, signal)
869}
870
871pub fn freeze_unit(params: Option<(UnitDBusLevel, String)>) -> Result<(), SystemdErrors> {
872    if let Some((_level, primary_name)) = params {
873        #[cfg(not(any(feature = "flatpak", feature = "appimage")))]
874        match _level {
875            UnitDBusLevel::System | UnitDBusLevel::Both => {
876                if proxy_switcher::PROXY_SWITCHER.freeze() {
877                    proxy_call_blocking!(freeze_unit, &primary_name)
878                } else {
879                    let proxy = systemd_manager();
880                    proxy.freeze_unit(&primary_name)?;
881                    Ok(())
882                }
883            }
884            UnitDBusLevel::UserSession => sysdbus::dbus_proxies::systemd_manager_session()
885                .freeze_unit(&primary_name)
886                .map_err(|err| err.into()),
887        }
888
889        #[cfg(any(feature = "flatpak", feature = "appimage"))]
890        {
891            let proxy = systemd_manager();
892            proxy.freeze_unit(&primary_name)?;
893            Ok(())
894        }
895    } else {
896        Err(SystemdErrors::NoUnit)
897    }
898}
899
900pub fn thaw_unit(params: Option<(UnitDBusLevel, String)>) -> Result<(), SystemdErrors> {
901    let Some((level, primary_name)) = params else {
902        return Err(SystemdErrors::NoUnit);
903    };
904
905    #[cfg(not(any(feature = "flatpak", feature = "appimage")))]
906    match level {
907        UnitDBusLevel::System | UnitDBusLevel::Both => {
908            if proxy_switcher::PROXY_SWITCHER.thaw() {
909                proxy_call_blocking!(thaw_unit, &primary_name)
910            } else {
911                let proxy = systemd_manager();
912                proxy.thaw_unit(&primary_name)?;
913                Ok(())
914            }
915        }
916        UnitDBusLevel::UserSession => sysdbus::dbus_proxies::systemd_manager_session()
917            .thaw_unit(&primary_name)
918            .map_err(|err| err.into()),
919    }
920
921    #[cfg(any(feature = "flatpak", feature = "appimage"))]
922    {
923        use crate::sysdbus::dbus_proxies::systemd_manager_blocking;
924        let proxy = systemd_manager_blocking(level);
925        proxy.thaw_unit(&primary_name)?;
926        Ok(())
927    }
928}
929
930pub fn reload_unit(
931    level: UnitDBusLevel,
932    primary_name: &str,
933    mode: StartStopMode,
934) -> Result<String, SystemdErrors> {
935    sysdbus::reload_unit(level, primary_name, mode.as_str())
936}
937
938pub fn queue_signal_unit(
939    level: UnitDBusLevel,
940    primary_name: &str,
941    who: KillWho,
942    signal: i32,
943    value: i32,
944) -> Result<(), SystemdErrors> {
945    sysdbus::queue_signal_unit(level, primary_name, who, signal, value)
946}
947
948pub fn clean_unit(
949    level: UnitDBusLevel,
950    unit_name: &str,
951    what: &[String],
952) -> Result<(), SystemdErrors> {
953    //just send all if seleted
954    let mut what_peekable = what
955        .iter()
956        .filter(|c_op| *c_op == CleanOption::All.code())
957        .peekable();
958
959    let clean_what: Vec<&str> = if what_peekable.peek().is_some() {
960        vec![CleanOption::All.code()]
961    } else {
962        what.iter().map(|s| s.as_str()).collect()
963    };
964
965    #[cfg(not(any(feature = "flatpak", feature = "appimage")))]
966    match level {
967        UnitDBusLevel::System | UnitDBusLevel::Both => {
968            if proxy_switcher::PROXY_SWITCHER.clean() {
969                proxy_call_blocking!(clean_unit, unit_name, &clean_what)
970            } else {
971                let proxy = systemd_manager();
972                proxy
973                    .clean_unit(unit_name, &clean_what)
974                    .map_err(|err| err.into())
975            }
976        }
977        UnitDBusLevel::UserSession => sysdbus::dbus_proxies::systemd_manager_session()
978            .clean_unit(unit_name, &clean_what)
979            .map_err(|err| err.into()),
980    }
981
982    #[cfg(any(feature = "flatpak", feature = "appimage"))]
983    {
984        use crate::sysdbus::dbus_proxies::systemd_manager_blocking;
985
986        systemd_manager_blocking(level)
987            .clean_unit(unit_name, &clean_what)
988            .map_err(|err| err.into())
989    }
990}
991
992pub fn mask_unit_files(
993    level: UnitDBusLevel,
994    primary_name: &str,
995    runtime: bool,
996    force: bool,
997) -> Result<Vec<DisEnAbleUnitFiles>, SystemdErrors> {
998    sysdbus::mask_unit_files(level, &[primary_name], runtime, force)
999}
1000
1001pub fn preset_unit_files(
1002    level: UnitDBusLevel,
1003    primary_name: &str,
1004    runtime: bool,
1005    force: bool,
1006) -> Result<DisEnAbleUnitFilesResponse, SystemdErrors> {
1007    sysdbus::preset_unit_file(level, &[primary_name], runtime, force)
1008}
1009
1010pub fn reenable_unit_file(
1011    level: UnitDBusLevel,
1012    primary_name: &str,
1013    runtime: bool,
1014    force: bool,
1015) -> Result<DisEnAbleUnitFilesResponse, SystemdErrors> {
1016    sysdbus::reenable_unit_file(level, &[primary_name], runtime, force)
1017}
1018
1019pub fn unmask_unit_files(
1020    level: UnitDBusLevel,
1021    primary_name: &str,
1022    runtime: bool,
1023) -> Result<Vec<DisEnAbleUnitFiles>, SystemdErrors> {
1024    sysdbus::unmask_unit_files(level, &[primary_name], runtime)
1025}
1026
1027pub fn link_unit_files(
1028    dbus_level: UnitDBusLevel,
1029    unit_file: &str,
1030    runtime: bool,
1031    force: bool,
1032) -> Result<Vec<DisEnAbleUnitFiles>, SystemdErrors> {
1033    sysdbus::link_unit_files(dbus_level, &[unit_file], runtime, force)
1034}
1035
1036pub async fn daemon_reload(level: UnitDBusLevel) -> Result<(), SystemdErrors> {
1037    let mut watcher = init_signal_watcher(level).await;
1038    daemon_reload_core(level).await?;
1039
1040    let mut wait_reload = async || {
1041        loop {
1042            match watcher.recv().await {
1043                Ok(SystemdSignal::Reloading(_, active)) => {
1044                    if active {
1045                        info!("Reloading!");
1046                    } else {
1047                        info!("Reload Finised");
1048                        break;
1049                    }
1050                }
1051                Ok(_) => {}
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}