Skip to content

Commit bdad438

Browse files
committed
Fix HOST var resolution
1 parent 074bd55 commit bdad438

File tree

3 files changed

+111
-23
lines changed

3 files changed

+111
-23
lines changed

src/backends/distrobox/distrobox.rs

Lines changed: 46 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use serde::{Deserialize, Deserializer};
44
use std::{
55
cell::LazyCell,
66
collections::BTreeMap,
7-
env,
7+
88
ffi::OsString,
99
io,
1010
os::unix::ffi::OsStringExt,
@@ -88,11 +88,10 @@ impl DesktopFiles {
8888
.collect()
8989
}
9090

91-
fn into_map(self) -> BTreeMap<PathBuf, String> {
91+
fn into_map(self, host_home: Option<PathBuf>) -> BTreeMap<PathBuf, String> {
9292
let mut desktop_files = self.system;
9393
// Only include user desktop files if the container's home directory is different from the host's
9494
// This avoids showing duplicate entries when the container shares the host's home directory
95-
let host_home = env::var("HOME").ok().map(PathBuf::from);
9695
if host_home.as_ref() != Some(&self.home_dir) {
9796
desktop_files.extend(self.user)
9897
}
@@ -471,15 +470,15 @@ impl DistroboxCommandRunnerResponse {
471470
) -> Vec<(Command, String)> {
472471
let mut commands = Vec::new();
473472

474-
// Get XDG_DATA_HOME
473+
// Get XDG_DATA_HOME (mocked via printenv)
475474
commands.push((
476-
Command::new_with_args("sh", ["-c", "echo $XDG_DATA_HOME"]),
475+
Command::new_with_args("printenv", ["XDG_DATA_HOME"]),
477476
String::new(),
478477
));
479478

480-
// Get HOME if XDG_DATA_HOME is empty
479+
// Get HOME if XDG_DATA_HOME is empty (mocked via printenv)
481480
commands.push((
482-
Command::new_with_args("sh", ["-c", "echo $HOME"]),
481+
Command::new_with_args("printenv", ["HOME"]),
483482
"/home/me".to_string(),
484483
));
485484

@@ -659,19 +658,31 @@ impl Distrobox {
659658
}
660659

661660
async fn host_applications_path(&self) -> Result<PathBuf, Error> {
662-
let mut cmd = Command::new("sh");
663-
cmd.args(["-c", "echo $XDG_DATA_HOME"]);
664-
let xdg_data_home = self.cmd_output_string(cmd).await?;
665-
666-
let xdg_data_home = if xdg_data_home.trim().is_empty() {
667-
let mut cmd = Command::new("sh");
668-
cmd.args(["-c", "echo $HOME"]);
669-
let home = self.cmd_output_string(cmd).await?;
670-
Path::new(home.trim()).join(".local/share")
661+
// Resolve XDG_DATA_HOME via runner (works in Flatpak via map_flatpak_spawn_host)
662+
let xdg_data_home_opt = match crate::fakers::resolve_host_env_via_runner(&self.cmd_runner, "XDG_DATA_HOME").await {
663+
Ok(Some(s)) if !s.trim().is_empty() => Some(Path::new(s.trim()).to_path_buf()),
664+
Ok(_) => None,
665+
Err(e) => {
666+
tracing::warn!("failed to resolve XDG_DATA_HOME via CommandRunner: {e:?}");
667+
None
668+
}
669+
};
670+
671+
let apps_base = if let Some(p) = xdg_data_home_opt {
672+
p
671673
} else {
672-
Path::new(xdg_data_home.trim()).to_path_buf()
674+
// Fallback to HOME
675+
match crate::fakers::resolve_host_env_via_runner(&self.cmd_runner, "HOME").await {
676+
Ok(Some(s)) if !s.trim().is_empty() => Path::new(s.trim()).join(".local/share"),
677+
Ok(_) => return Err(Error::ResolveHostPath("XDG_DATA_HOME and HOME are not set on the host".into())),
678+
Err(e) => {
679+
tracing::warn!("failed to resolve HOME via CommandRunner: {e:?}");
680+
return Err(Error::ResolveHostPath("failed to resolve host HOME".into()));
681+
}
682+
}
673683
};
674-
let apps_path = xdg_data_home.join("applications");
684+
685+
let apps_path = apps_base.join("applications");
675686
Ok(apps_path)
676687
}
677688
async fn get_exported_desktop_files(&self) -> Result<Vec<String>, Error> {
@@ -702,8 +713,18 @@ impl Distrobox {
702713
.map_err(|e| Error::ParseOutput(format!("{e:?}")))?;
703714
debug!(desktop_files = format_args!("{desktop_files:#?}"));
704715

716+
// Resolve host HOME via CommandRunner so this works inside Flatpak as well
717+
let host_home_opt = match crate::fakers::resolve_host_env_via_runner(&self.cmd_runner, "HOME").await {
718+
Ok(Some(s)) => Some(PathBuf::from(s)),
719+
Ok(None) => None,
720+
Err(e) => {
721+
tracing::warn!("failed to resolve host HOME via CommandRunner: {e:?}");
722+
None
723+
}
724+
};
725+
705726
Ok(desktop_files
706-
.into_map()
727+
.into_map(host_home_opt)
707728
.into_iter()
708729
.map(|(path, content)| (path.to_string_lossy().into_owned(), content))
709730
.collect::<Vec<_>>())
@@ -1182,8 +1203,9 @@ Categories=Utility;Network;";
11821203

11831204
let db = Distrobox::new(
11841205
NullCommandRunnerBuilder::new()
1185-
.cmd(&["sh", "-c", "echo $XDG_DATA_HOME"], "")
1186-
.cmd(&["sh", "-c", "echo $HOME"], "/home/me")
1206+
.cmd(&["printenv", "HOME"], "/home/me")
1207+
.cmd(&["printenv", "XDG_DATA_HOME"], "")
1208+
.cmd(&["printenv", "HOME"], "/home/me")
11871209
.cmd(
11881210
&["ls", "/home/me/.local/share/applications"],
11891211
"ubuntu-vim.desktop\n",
@@ -1235,8 +1257,9 @@ Categories=Utility;Security;";
12351257

12361258
let db = Distrobox::new(
12371259
NullCommandRunnerBuilder::new()
1238-
.cmd(&["sh", "-c", "echo $XDG_DATA_HOME"], "")
1239-
.cmd(&["sh", "-c", "echo $HOME"], "/home/me")
1260+
.cmd(&["printenv", "HOME"], "/home/me")
1261+
.cmd(&["printenv", "XDG_DATA_HOME"], "")
1262+
.cmd(&["printenv", "HOME"], "/home/me")
12401263
.cmd(
12411264
&["ls", "/home/me/.local/share/applications"],
12421265
"ubuntu-Proton Authenticator.desktop\n",

src/fakers/host_env.rs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
use crate::fakers::{Command, CommandRunner};
2+
use std::io;
3+
4+
/// Resolve an environment variable on the host via a `CommandRunner`.
5+
///
6+
/// Returns `Ok(Some(value))` if present, `Ok(None)` if unset/empty, or `Err(e)` on I/O/command errors.
7+
pub async fn resolve_host_env_via_runner(
8+
runner: &CommandRunner,
9+
key: &str,
10+
) -> io::Result<Option<String>> {
11+
// Try `printenv KEY` first — simpler when available
12+
let cmd = Command::new_with_args("printenv", [key]);
13+
match runner.output_string(cmd.clone()).await {
14+
Ok(out) => {
15+
let trimmed = out.trim().to_string();
16+
if !trimmed.is_empty() {
17+
return Ok(Some(trimmed));
18+
}
19+
// fallthrough to sh -c printf if empty
20+
}
21+
Err(_) => {
22+
// fallback below
23+
}
24+
}
25+
26+
// Fallback: sh -c 'printf "%s" "$KEY"'
27+
let mut cmd = Command::new("sh");
28+
cmd.arg("-c");
29+
// Only interpolate the variable name to avoid injection
30+
cmd.arg(format!("printf '%s' \"${}\"", key));
31+
32+
let out = runner.output_string(cmd).await?;
33+
let trimmed = out.trim().to_string();
34+
if trimmed.is_empty() {
35+
Ok(None)
36+
} else {
37+
Ok(Some(trimmed))
38+
}
39+
}
40+
41+
#[cfg(test)]
42+
mod tests {
43+
use super::*;
44+
use crate::fakers::NullCommandRunnerBuilder;
45+
use smol::block_on;
46+
47+
#[test]
48+
fn test_resolve_host_env_with_null_runner() {
49+
let runner = NullCommandRunnerBuilder::new()
50+
.cmd(&["printenv", "HOME"], "/fake/home")
51+
.build();
52+
53+
let res = block_on(resolve_host_env_via_runner(&runner, "HOME")).unwrap();
54+
assert_eq!(res, Some("/fake/home".to_string()));
55+
}
56+
57+
#[test]
58+
fn test_resolve_host_env_unset_with_null_runner() {
59+
let runner = NullCommandRunnerBuilder::new().build();
60+
let res = block_on(resolve_host_env_via_runner(&runner, "HOME")).unwrap();
61+
assert_eq!(res, None);
62+
}
63+
}

src/fakers/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
mod command;
22
mod command_runner;
3+
mod host_env;
34
mod output_tracker;
45

56
pub use command::{Command, FdMode};
67
pub use command_runner::{Child, CommandRunner, CommandRunnerEvent, NullCommandRunnerBuilder};
78
pub use output_tracker::OutputTracker;
9+
pub use host_env::resolve_host_env_via_runner;

0 commit comments

Comments
 (0)