Skip to main content

sysd_manager_base/
file.rs

1use std::ffi::OsStr;
2use std::{
3    error::Error,
4    io,
5    path::{Path, PathBuf},
6    sync::OnceLock,
7};
8use tokio::fs;
9use tokio::io::AsyncWriteExt;
10use tokio::process::Command;
11
12#[allow(unused_imports)]
13use tracing::{error, info, warn};
14
15pub fn determine_drop_in_path_dir(
16    unit_name: &str,
17    runtime: bool,
18    user_session: bool,
19) -> Result<String, Box<dyn Error + 'static>> {
20    let path = match (runtime, user_session) {
21        (true, false) => format!("/run/systemd/system/{}.d", unit_name),
22        (false, false) => format!("/etc/systemd/system/{}.d", unit_name),
23        (true, true) => {
24            let runtime_dir = std::env::var("XDG_RUNTIME_DIR")
25                .unwrap_or_else(|_| format!("/run/user/{}", unsafe { libc::getuid() }));
26
27            format!("{runtime_dir}/systemd/user/{}.d", unit_name)
28        }
29        (false, true) => {
30            let home_dir = std::env::home_dir().ok_or(Box::<dyn Error>::from(
31                "No HOME found to create drop-in".to_string(),
32            ))?;
33            format!(
34                "{}/.config/systemd/user/{}.d",
35                home_dir.display(),
36                unit_name
37            )
38        }
39    };
40    Ok(path)
41}
42
43pub fn create_drop_in_path_file(
44    unit_name: &str,
45    runtime: bool,
46    user_session: bool,
47    file_name: &str,
48) -> Result<String, Box<dyn Error + 'static>> {
49    let path_dir = determine_drop_in_path_dir(unit_name, runtime, user_session)?;
50
51    let path = format!("{path_dir}/{file_name}.conf");
52
53    info!(
54        "Creating drop-in path for unit: {}, runtime: {}, user: {} -> path {}",
55        unit_name, runtime, user_session, path
56    );
57    Ok(path)
58}
59
60pub async fn create_drop_in_io(file_path_str: &str, content: &str) -> Result<(), std::io::Error> {
61    if file_path_str.contains("../") {
62        let err = std::io::Error::new(
63            std::io::ErrorKind::InvalidData,
64            r#"The "../" patern is not supported""#,
65        );
66
67        return Err(err);
68    }
69
70    let file_path = PathBuf::from(file_path_str);
71
72    let unit_drop_in_dir = file_path.parent().ok_or(std::io::Error::new(
73        std::io::ErrorKind::InvalidData,
74        format!("Parent dir of file {:?} is invalid", file_path_str),
75    ))?;
76
77    if !unit_drop_in_dir.exists() {
78        info!("Creating dir {}", unit_drop_in_dir.display());
79
80        fs::create_dir_all(&unit_drop_in_dir).await?;
81    }
82
83    //Save content
84    info!("Creating file {}", file_path.display());
85    let bytes_written = save_io(&file_path, true, content).await?;
86
87    info!(
88        "{bytes_written} bytes writen on File {}",
89        file_path.to_string_lossy()
90    );
91    Ok(())
92}
93
94pub async fn save_io(
95    file_path: impl AsRef<Path>,
96    create: bool,
97    content: &str,
98) -> Result<u64, std::io::Error> {
99    let mut file = fs::OpenOptions::new()
100        .write(true)
101        .truncate(true)
102        .create(create)
103        .open(file_path)
104        .await?;
105
106    let test_bytes = content.as_bytes();
107
108    file.write_all(test_bytes).await?;
109    file.flush().await?;
110
111    let bytes_written = test_bytes.len();
112
113    Ok(bytes_written as u64)
114}
115
116#[macro_export]
117macro_rules! args {
118    ($($a:expr),*) => {
119        [
120            $(AsRef::<OsStr>::as_ref(&$a),)*
121        ]
122    }
123}
124
125#[macro_export]
126macro_rules! vs {
127    ($($a:expr),*) => {
128        [
129            $(AsRef::<String>::as_ref(&$a),)*
130        ]
131    }
132}
133
134pub const FLATPAK_SPAWN: &str = "flatpak-spawn";
135
136pub static INSIDE_FLATPAK: OnceLock<bool> = OnceLock::new();
137
138#[macro_export]
139macro_rules! inside_flatpak {
140    () => {
141        *INSIDE_FLATPAK.get_or_init(|| {
142            #[cfg(not(feature = "flatpak"))]
143            warn!("Not supposed to be called");
144
145            let in_flatpak = std::env::var("FLATPAK_ID").is_ok();
146
147            #[cfg(feature = "flatpak")]
148            if !in_flatpak {
149                warn!("Your run the flatpak compilation, but you aren't running inside a Flatpak");
150            }
151
152            in_flatpak
153        })
154    };
155}
156
157pub fn inside_flatpak() -> bool {
158    inside_flatpak!()
159}
160
161/*     pub fn args<I, S>(&mut self, args: I) -> &mut Command
162where
163    I: IntoIterator<Item = S>,
164    S: AsRef<OsStr>, */
165
166#[cfg(feature = "flatpak")]
167pub fn commander<I, S>(prog_n_args: I, environment_variables: Option<&[(&str, &str)]>) -> Command
168where
169    I: IntoIterator<Item = S>,
170    S: AsRef<OsStr>,
171{
172    if !inside_flatpak!() {
173        error!("Command call might not work because you are not running inside a Flatpak")
174    }
175
176    let mut cmd = Command::new(FLATPAK_SPAWN);
177    cmd.arg("--host");
178    cmd.args(prog_n_args);
179
180    if let Some(envs) = environment_variables {
181        for env in envs {
182            cmd.arg(format!("--env={}={}", env.0, env.1));
183        }
184    }
185
186    cmd
187}
188
189#[cfg(not(feature = "flatpak"))]
190pub fn commander<I, S>(prog_n_args: I, environment_variables: Option<&[(&str, &str)]>) -> Command
191where
192    I: IntoIterator<Item = S>,
193    S: AsRef<OsStr>,
194{
195    let mut it = prog_n_args.into_iter();
196    let mut cmd = Command::new(it.next().unwrap());
197
198    for arg in it {
199        cmd.arg(arg);
200    }
201
202    if let Some(envs) = environment_variables {
203        for env in envs {
204            cmd.env(env.0, env.1);
205        }
206    }
207
208    cmd
209}
210
211pub fn commander_blocking<I, S>(
212    prog_n_args: I,
213    environment_variables: Option<&[(&str, &str)]>,
214) -> std::process::Command
215where
216    I: IntoIterator<Item = S>,
217    S: AsRef<OsStr>,
218{
219    commander(prog_n_args, environment_variables).into_std()
220}
221
222pub fn test_flatpak_spawn() -> Result<(), io::Error> {
223    #[cfg(feature = "flatpak")]
224    {
225        info!("test_flatpak_spawn");
226        std::process::Command::new(FLATPAK_SPAWN)
227            .arg("--help")
228            .output()
229            .map(|_o| ())
230    }
231
232    #[cfg(not(feature = "flatpak"))]
233    Ok(())
234}
235
236/// To be able to acces the Flatpack mounted files.
237/// Limit to /usr for the least access principle
238pub fn flatpak_host_file_path(file_path: &str) -> PathBuf {
239    #[cfg(feature = "flatpak")]
240    {
241        if inside_flatpak!() && (file_path.starts_with("/usr") || file_path.starts_with("/etc")) {
242            let file_path = file_path.strip_prefix('/').unwrap_or(file_path);
243            PathBuf::from_iter(["/run/host", file_path])
244        } else {
245            PathBuf::from(&file_path)
246        }
247    }
248
249    #[cfg(not(feature = "flatpak"))]
250    PathBuf::from(file_path)
251}
252
253#[cfg(test)]
254mod test {
255    use super::*;
256    use test_base::init_logs;
257
258    pub fn flatpak_host_file_path_t(file_path: &str) -> PathBuf {
259        let file_path = if let Some(stripped) = file_path.strip_prefix('/') {
260            stripped
261        } else {
262            file_path
263        };
264        PathBuf::from_iter(["/run/host", file_path])
265    }
266
267    pub fn flatpak_host_file_path_t2(file_path: &str) -> PathBuf {
268        PathBuf::from("/run/host").join(file_path)
269    }
270
271    #[test]
272    fn test_fp() {
273        init_logs();
274
275        let src = PathBuf::from("/tmp");
276        let a = flatpak_host_file_path(&src.to_string_lossy());
277        warn!("{} exists {}", a.display(), a.exists());
278        warn!("{} exists {}", src.display(), src.exists());
279    }
280
281    #[test]
282    fn test_fp2() {
283        init_logs();
284
285        let src = PathBuf::from("/tmp");
286        let a = flatpak_host_file_path_t(&src.to_string_lossy());
287        warn!("{} exists {}", a.display(), a.exists());
288        warn!("{} exists {}", src.display(), src.exists());
289
290        let b = flatpak_host_file_path_t("test");
291        warn!("{} exists {}", b.display(), b.exists());
292
293        let b = flatpak_host_file_path_t("/test");
294        warn!("{} exists {}", b.display(), b.exists());
295
296        let b = flatpak_host_file_path_t2("/test");
297        warn!("{} exists {}", b.display(), b.exists());
298    }
299}