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 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#[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
236pub 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}