Skip to content

Commit 3ecd324

Browse files
committed
is_nvidia_host heuristic, install systemd when init, refactor path resolution
1 parent b79fe91 commit 3ecd324

File tree

3 files changed

+130
-70
lines changed

3 files changed

+130
-70
lines changed

src/create_distrobox_dialog.rs

Lines changed: 33 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,20 @@ impl CreateDistroboxDialog {
330330
));
331331
this.root_store().images().reload();
332332

333+
glib::MainContext::ref_thread_default().spawn_local(clone!(
334+
#[weak]
335+
this,
336+
async move {
337+
this.root_store()
338+
.is_nvidia_host()
339+
.await
340+
.ok()
341+
.map(|is_nvidia| {
342+
this.imp().nvidia_row.set_active(is_nvidia);
343+
});
344+
}
345+
));
346+
333347
this
334348
}
335349

@@ -358,26 +372,25 @@ impl CreateDistroboxDialog {
358372
move |res: Result<File, _>| {
359373
if let Ok(file) = res {
360374
if let Some(path) = file.path() {
361-
362-
glib::MainContext::ref_thread_default().spawn_local(async move {
363-
match this
364-
.root_store()
365-
.resolve_host_path(&path.display().to_string())
366-
.await
367-
{
368-
Ok(resolved_path) => {
369-
row.set_subtitle(&resolved_path);
370-
cb(PathBuf::from(resolved_path));
371-
}
372-
373-
Err(e) => {
374-
this.update_errors::<()>(&Err(Error::InvalidField(
375-
title.to_lowercase(),
376-
e.to_string(),
377-
)));
375+
glib::MainContext::ref_thread_default().spawn_local(async move {
376+
match this
377+
.root_store()
378+
.resolve_host_path(&path.display().to_string())
379+
.await
380+
{
381+
Ok(resolved_path) => {
382+
row.set_subtitle(&resolved_path);
383+
cb(PathBuf::from(resolved_path));
384+
}
385+
386+
Err(e) => {
387+
this.update_errors::<()>(&Err(Error::InvalidField(
388+
title.to_lowercase(),
389+
e.to_string(),
390+
)));
391+
}
378392
}
379-
}
380-
});
393+
});
381394
}
382395
}
383396
}
@@ -428,21 +441,11 @@ impl CreateDistroboxDialog {
428441

429442
let name = CreateArgName::new(&imp.name_row.text())?;
430443

431-
dbg!(&self.home_folder());
432444
let create_args = CreateArgs {
433445
name,
434446
image: image.to_string(),
435447
nvidia: imp.nvidia_row.is_active(),
436-
home_path: if let Some(home) = self.home_folder() {
437-
Some(
438-
self.root_store()
439-
.resolve_host_path(&home)
440-
.await
441-
.map_err(|e| Error::InvalidField("home".to_string(), e.to_string()))?,
442-
)
443-
} else {
444-
None
445-
},
448+
home_path: self.home_folder(),
446449
init: imp.init_row.is_active(),
447450
volumes,
448451
};

src/distrobox/mod.rs

Lines changed: 35 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,23 @@
1+
use crate::fakers::{
2+
Child, Command, CommandRunner, FdMode, InnerCommandRunner, NullCommandRunnerBuilder,
3+
};
14
use std::{
2-
cell::LazyCell, collections::BTreeMap, future::Future, io, path::{Path, PathBuf}, pin::Pin, process::Output, rc::Rc, str::FromStr
5+
cell::LazyCell,
6+
collections::BTreeMap,
7+
future::Future,
8+
io,
9+
path::{Path, PathBuf},
10+
pin::Pin,
11+
process::Output,
12+
rc::Rc,
13+
str::FromStr,
314
};
415
use tracing::{debug, error, info, warn};
5-
use crate::fakers::{CommandRunner, NullCommandRunnerBuilder, Command, Child, InnerCommandRunner, FdMode};
616

717
mod desktop_file;
818

919
pub use desktop_file::*;
1020

11-
1221
#[derive(Clone)]
1322
pub struct FlatpakCommandRunner {
1423
pub command_runner: Rc<dyn InnerCommandRunner>,
@@ -40,7 +49,6 @@ impl InnerCommandRunner for FlatpakCommandRunner {
4049
}
4150
}
4251

43-
4452
pub struct Distrobox {
4553
cmd_runner: CommandRunner,
4654
}
@@ -468,14 +476,10 @@ impl DistroboxCommandRunnerResponse {
468476

469477
impl Distrobox {
470478
pub fn new(cmd_runner: CommandRunner) -> Self {
471-
Self {
472-
cmd_runner,
473-
}
479+
Self { cmd_runner }
474480
}
475481

476-
pub fn null_command_runner(
477-
responses: &[DistroboxCommandRunnerResponse],
478-
) -> CommandRunner {
482+
pub fn null_command_runner(responses: &[DistroboxCommandRunnerResponse]) -> CommandRunner {
479483
let mut builder = NullCommandRunnerBuilder::new();
480484
for res in responses {
481485
for (cmd, out) in res.clone().to_commands() {
@@ -700,7 +704,10 @@ impl Distrobox {
700704
));
701705
}
702706
let mut cmd = dbcmd();
703-
cmd.arg("assemble").arg("create").arg("--file").arg(file_path);
707+
cmd.arg("assemble")
708+
.arg("create")
709+
.arg("--file")
710+
.arg(file_path);
704711
self.cmd_spawn(cmd)
705712
}
706713

@@ -726,7 +733,9 @@ impl Distrobox {
726733
cmd.arg("--name").arg(args.name.0);
727734
}
728735
if args.init {
729-
cmd.arg("--init");
736+
cmd.arg("--init")
737+
.arg("--additional-packages")
738+
.arg("systemd");
730739
}
731740
if args.nvidia {
732741
cmd.arg("--nvidia");
@@ -883,12 +892,15 @@ d24405b14180 | ubuntu | Created | ghcr.io/ublue-os/ubun
883892
);
884893
assert_eq!(
885894
db.list().await?,
886-
BTreeMap::from_iter([("ubuntu".into(), ContainerInfo {
887-
id: "d24405b14180".into(),
888-
name: "ubuntu".into(),
889-
status: Status::Created("".into()),
890-
image: "ghcr.io/ublue-os/ubuntu-toolbox:latest".into(),
891-
})])
895+
BTreeMap::from_iter([(
896+
"ubuntu".into(),
897+
ContainerInfo {
898+
id: "d24405b14180".into(),
899+
name: "ubuntu".into(),
900+
status: Status::Created("".into()),
901+
image: "ghcr.io/ublue-os/ubuntu-toolbox:latest".into(),
902+
}
903+
)])
892904
);
893905
Ok(())
894906
})
@@ -986,8 +998,11 @@ Categories=Utility;Network;
986998
..Default::default()
987999
};
9881000
smol::block_on(db.create(args))?;
989-
let expected = "distrobox create --yes --image docker.io/library/ubuntu:latest --init --nvidia --home /home/me --volume /mnt/sdb1:/mnt/sdb1 --volume /mnt/sdb4:/mnt/sdb4:ro";
990-
assert_eq!(output_tracker.items()[0].command().unwrap().to_string(), expected);
1001+
let expected = "distrobox create --yes --image docker.io/library/ubuntu:latest --init --additional-packages systemd --nvidia --home /home/me --volume /mnt/sdb1:/mnt/sdb1 --volume /mnt/sdb4:/mnt/sdb4:ro";
1002+
assert_eq!(
1003+
output_tracker.items()[0].command().unwrap().to_string(),
1004+
expected
1005+
);
9911006
Ok(())
9921007
}
9931008
#[test]

src/store/root_store.rs

Lines changed: 62 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ use std::cell::OnceCell;
1010
use std::cell::RefCell;
1111
use std::path::Path;
1212
use std::time::Duration;
13-
use tracing::debug;
1413
use tracing::error;
1514
use tracing::info;
15+
use tracing::{debug, warn};
1616

1717
use crate::container::Container;
1818
use crate::distrobox;
@@ -386,8 +386,46 @@ impl RootStore {
386386
});
387387
}
388388

389+
pub async fn run_to_string(&self, mut cmd: Command) -> Result<String, anyhow::Error> {
390+
cmd.stderr = FdMode::Pipe;
391+
cmd.stdout = FdMode::Pipe;
392+
let output = self.command_runner().output(cmd.clone()).await?;
393+
Ok(String::from_utf8(output.stdout).map_err(|e| {
394+
error!(cmd = %cmd, "Failed to parse command output");
395+
distrobox::Error::ParseOutput(e.to_string())
396+
})?)
397+
}
398+
399+
pub async fn is_nvidia_host(&self) -> Result<bool, distrobox::Error> {
400+
// uses lspci to check if the host has an NVIDIA GPU
401+
debug!("Checking if host is NVIDIA");
402+
let cmd = Command::new("lspci");
403+
let output = self.run_to_string(cmd).await;
404+
match output {
405+
Ok(output) => {
406+
let is_nvidia = output.contains("NVIDIA") || output.contains("nVidia");
407+
debug!(is_nvidia, "Checked if host is NVIDIA");
408+
Ok(is_nvidia)
409+
}
410+
Err(e) => {
411+
debug!(?e, "Failed to check if host is NVIDIA");
412+
Ok(false) // If we can't run lspci, we assume it's not NVIDIA
413+
}
414+
}
415+
}
416+
389417
pub async fn resolve_host_path(&self, path: &str) -> Result<String, distrobox::Error> {
390-
let mut cmd = Command::new_with_args(
418+
// The path could be a:
419+
// 1. Host path, already resolved to a real location, e.g., "/home/user/Documents/custom-home-folder".
420+
// 2. Path from a flatpak sandbox, e.g., "/run/user/1000/doc/abc123".
421+
// The user may not have the `getfattr`, but we still want to try using it,
422+
// because we don't have an exact way to know if the path is from a flatpak sandbox or not.
423+
// If the path is already a real host path, `getfattr` may return an empty output,
424+
// because it doesn't have the `user.document-portal.host-path` attribute set by the flatpak portal.
425+
426+
debug!(?path, "Resolving host path");
427+
428+
let cmd = Command::new_with_args(
391429
"getfattr",
392430
[
393431
"-n",
@@ -396,30 +434,34 @@ impl RootStore {
396434
path,
397435
],
398436
);
399-
cmd.stderr = FdMode::Pipe;
400-
cmd.stdout = FdMode::Pipe;
401437
let output = self
402-
.command_runner()
403-
.output(cmd)
438+
.run_to_string(cmd)
404439
.await
405440
.map_err(|e| distrobox::Error::ResolveHostPath(e.to_string()));
406441

407442
let is_from_sandbox = path.starts_with("/run/user");
408443

409-
// If the path is not from a flatpak sandbox, we assume it's a regular path, so we can skip the getfattr command error.
410-
// If the command was successful, but for some reason the output is empty, we also return the path as is.
411-
let stdout = if (output.is_err() && !is_from_sandbox)
412-
|| output.as_ref().map_or(false, |o| o.stdout.is_empty())
413-
{
414-
return Ok(path.to_string());
415-
} else {
416-
output?.stdout
417-
};
418-
419-
Ok(String::from_utf8(stdout)
420-
.map_err(|e| distrobox::Error::ParseOutput(e.to_string()))?
421-
.trim()
422-
.to_string())
444+
match output {
445+
Ok(resolved_path) => {
446+
debug!(?resolved_path, "Resolved host path");
447+
if resolved_path.is_empty() {
448+
// If the output is empty, we assume the path is already a real host path.
449+
return Ok(path.to_string());
450+
}
451+
Ok(resolved_path.trim().to_string())
452+
}
453+
Err(e) if !is_from_sandbox => {
454+
debug!(
455+
?e,
456+
"Failed to execute getfattr, but path doesn't seem from a sandbox anyway"
457+
);
458+
Ok(path.trim().to_string())
459+
}
460+
Err(e) => {
461+
debug!(?e, "Failed to resolve host path using getfattr");
462+
Err(e)
463+
}
464+
}
423465
}
424466
}
425467

0 commit comments

Comments
 (0)