Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 10 additions & 47 deletions daemon/src/comm/dbus/control_interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,13 @@
//!

use crate::comm::dbus::validate_device_handle;
use crate::error::map_error_io_to_fdo;
use crate::platforms::platform::{platform_for_known_platform, platform_from_compat_or_device};
use crate::platforms::universal::universal_write_handler;
use crate::softeners::error::FpgadSoftenerError;
#[cfg(feature = "xilinx-dfx-mgr")]
use crate::softeners::xilinx_dfx_mgr::xilinx_dfx_mgr_helpers::run_dfx_mgr;
use log::{info, trace};
use std::env;
use std::path::Path;
use std::sync::Arc;
use tokio::process::Command;
use tokio::sync::{Mutex, MutexGuard, OnceCell};
use zbus::{fdo, interface};

Expand Down Expand Up @@ -340,51 +338,16 @@ impl ControlInterface {
/// ```
async fn dfx_mgr(&self, cmd_string: &str) -> Result<String, fdo::Error> {
if cfg!(feature = "xilinx-dfx-mgr") {
let snap_env = env::var("SNAP").unwrap_or("".to_string());
let args: Vec<&str> = cmd_string.split_whitespace().collect();

let dfx_mgr_client_path = format!("{}/usr/bin/dfx-mgr-client", snap_env);

// Check if dfx-mgr-client exists
if !Path::new(&dfx_mgr_client_path).exists() {
return Err(FpgadSoftenerError::DfxMgr(format!(
"dfx-mgr-client not detected.\n\
If using snap, please install the dfx-mgr component with \n\
`[sudo] snap install fpgad+dfx-mgr [options]` \n\
otherwise ensure that dfx-mgr-client exists at `{dfx_mgr_client_path}`"
))
.into());
}

let output = Command::new(&dfx_mgr_client_path)
.args(cmd_string.split_whitespace())
.output()
.await
.map_err(|e| {
map_error_io_to_fdo("dfx-mgr-client call failed to produce any output", e)
})?;

// Exit status
match output.status.success() {
true => {
info!("Command ran successfully!");
Ok(format!(
"dfx-mgr called with args {}.\nExit status: {}\nStdout:\n{}\nStderr:\n{}",
cmd_string,
output.status,
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
))
match run_dfx_mgr(&args) {
Ok(output) => {
info!("dfx-mgr command ran successfully!");
Ok(output)
}
false => {
info!("Command failed with code: {:#?}", output.status.code());
Err(FpgadSoftenerError::DfxMgr(format!(
"dfx-mgr called with args {}.\nExit status: {}\nStdout:\n{}\nStderr:\n{}",
cmd_string,
output.status,
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
))
.into())
Err(e) => {
info!("dfx-mgr command failed: {}", e);
Err(e.into())
}
}
} else {
Expand Down
14 changes: 0 additions & 14 deletions daemon/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,17 +131,3 @@ impl From<FpgadError> for fdo::Error {
}
}
}

/// Converter for tokio::io::Error into zbus::fdo::Error types, with added "custom_msg" in order
/// to provide additional context.
/// This allows for "?" on e.g., tokio::io::Command calls
///
/// # Arguments
///
/// * `custom_msg`: String to prepend the error - use to add context e.g. "failed when calling nproc"
/// * `err`: the tokio::io::error from the failing function
///
/// returns: zbus::fdo::Error type suitable for dbus transmission
pub(crate) fn map_error_io_to_fdo(custom_msg: &str, err: impl std::fmt::Display) -> fdo::Error {
fdo::Error::Failed(format!("{custom_msg}:\n{err}"))
}
40 changes: 1 addition & 39 deletions daemon/src/softeners/xilinx_dfx_mgr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@
//! # }
//! ```

use std::env;
use std::path::Path;
use std::sync::OnceLock;

Expand All @@ -65,6 +64,7 @@ use crate::softeners::error::FpgadSoftenerError;
use fpgad_macros::platform;
use log::trace;
use xilinx_dfx_mgr_fpga::XilinxDfxMgrFPGA;
use xilinx_dfx_mgr_helpers::run_dfx_mgr;
use xilinx_dfx_mgr_overlay_handler::XilinxDfxMgrOverlayHandler;

// Marked as public so that the members are published in docs
Expand Down Expand Up @@ -217,41 +217,3 @@ pub fn load_overlay(bitstream_path: &Path, dtbo_path: &Path) -> Result<String, F

run_dfx_mgr(&["-o", dtbo_str, "-b", bitstream_str])
}

/// Helper to run the dfx-mgr-client binary with arguments
fn run_dfx_mgr(args: &[&str]) -> Result<String, FpgadSoftenerError> {
let prefix = if let Ok(snap_env) = env::var("SNAP_COMPONENTS") {
snap_env + "/dfx-mgr"
} else {
"".to_string()
};

let dfx_mgr_client_path = format!("{}/usr/bin/dfx-mgr-client", prefix);

// Check if dfx-mgr-client exists before trying to run it
if !Path::new(&dfx_mgr_client_path).exists() {
return Err(FpgadSoftenerError::DfxMgr(format!(
"dfx-mgr-client not found at '{}'. Install the dfx-mgr component with: snap install fpgad+dfx-mgr.comp",
dfx_mgr_client_path
)));
}

trace!("Calling dfx-mgr with args {:#?}", args);
let output = std::process::Command::new(&dfx_mgr_client_path)
.args(args)
.output()
.map_err(|e| {
FpgadSoftenerError::DfxMgr(format!("dfx-mgr-client failed to produce output:\n{e}"))
})?;
if output.status.success() {
Ok(String::from_utf8_lossy(&output.stdout).to_string())
} else {
// output.status usually looks like `exit status: 255`
Err(FpgadSoftenerError::DfxMgr(format!(
"dfx-mgr-client failed.\n{}\nStdout:\n{:?}\nStderr:\n{:?}",
output.status,
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
)))
}
}
108 changes: 108 additions & 0 deletions daemon/src/softeners/xilinx_dfx_mgr/xilinx_dfx_mgr_helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
//!
//! # Key Functions
//!
//! - [`get_dfx_mgr_client_path`] - Locates and validates the dfx-mgr-client binary path
//! - [`run_dfx_mgr`] - Executes dfx-mgr-client synchronously with given arguments
//! - [`extract_firmware_name`] - Parses .dtbo files to extract the firmware-name property
//!
//! # Device Tree Parsing
Expand All @@ -40,8 +42,114 @@
use crate::error::FpgadError;
use crate::softeners::error::FpgadSoftenerError;
use log::trace;
use std::env;
use std::fs;
use std::path::Path;
use std::process::Command;

/// Get the path to the dfx-mgr-client binary and verify it exists.
///
/// Checks for dfx-mgr-client in the following order:
/// 1. In snap component: `$SNAP_COMPONENTS/dfx-mgr/usr/bin/dfx-mgr-client`
/// 2. In snap itself: `$SNAP/usr/bin/dfx-mgr-client`
///
/// # Returns
/// * `Ok(String)` - The path to dfx-mgr-client if it exists
/// * `Err(FpgadSoftenerError)` - If dfx-mgr-client cannot be found or doesn't exist
///
/// # Examples
///
/// ```rust,no_run
/// # use daemon::softeners::xilinx_dfx_mgr::xilinx_dfx_mgr_helpers::get_dfx_mgr_client_path;
/// # fn example() -> Result<(), daemon::softeners::error::FpgadSoftenerError> {
/// let path = get_dfx_mgr_client_path()?;
/// println!("dfx-mgr-client is at: {}", path);
/// # Ok(())
/// # }
/// ```
pub fn get_dfx_mgr_client_path() -> Result<String, FpgadSoftenerError> {
// Check for dfx-mgr-client in snap component first, then fall back to snap itself
let dfx_mgr_client_path = if let Ok(snap_components) = env::var("SNAP_COMPONENTS") {
let component_path = format!("{}/dfx-mgr/usr/bin/dfx-mgr-client", snap_components);
if Path::new(&component_path).exists() {
component_path
} else if let Ok(snap_env) = env::var("SNAP") {
// Fall back to snap path
format!("{}/usr/bin/dfx-mgr-client", snap_env)
} else {
return Err(FpgadSoftenerError::DfxMgr(
"SNAP_COMPONENTS and SNAP environment variables not set. \
Not running in a snap environment?"
.to_string(),
));
}
} else if let Ok(snap_env) = env::var("SNAP") {
// Not using components, use snap path
format!("{}/usr/bin/dfx-mgr-client", snap_env)
} else {
return Err(FpgadSoftenerError::DfxMgr(
"SNAP environment variable not set. Not running in a snap environment?".to_string(),
));
};

// Check if dfx-mgr-client exists
if !Path::new(&dfx_mgr_client_path).exists() {
return Err(FpgadSoftenerError::DfxMgr(format!(
"dfx-mgr-client not found at '{}'.\n\n\
To enable Xilinx DFX Manager support, install the dfx-mgr component:\n\n\
sudo snap install fpgad+dfx-mgr.comp --dangerous\n\n\
Or run the CLI using the `universal` platform (no dfx-mgr required):\n\n\
fpgad --platform=universal <command>\n\n\
If you are calling the daemon over DBus, set the platform_string to `universal` instead.",
dfx_mgr_client_path
)));
}

Ok(dfx_mgr_client_path)
}

/// Run dfx-mgr-client with the given arguments (synchronous version).
///
/// # Arguments
///
/// * `args` - Command line arguments to pass to dfx-mgr-client
///
/// # Returns
/// * `Ok(String)` - stdout from dfx-mgr-client on success
/// * `Err(FpgadSoftenerError)` - If dfx-mgr-client cannot be found or execution fails
///
/// # Examples
///
/// ```rust,no_run
/// # use daemon::softeners::xilinx_dfx_mgr::xilinx_dfx_mgr_helpers::run_dfx_mgr;
/// # fn example() -> Result<(), daemon::softeners::error::FpgadSoftenerError> {
/// let output = run_dfx_mgr(&["-listPackage"])?;
/// println!("Packages: {}", output);
/// # Ok(())
/// # }
/// ```
pub fn run_dfx_mgr(args: &[&str]) -> Result<String, FpgadSoftenerError> {
let dfx_mgr_client_path = get_dfx_mgr_client_path()?;

trace!("Calling dfx-mgr-client with args {:#?}", args);
let output = Command::new(&dfx_mgr_client_path)
.args(args)
.output()
.map_err(|e| {
FpgadSoftenerError::DfxMgr(format!("dfx-mgr-client failed to produce output:\n{e}"))
})?;

if output.status.success() {
Ok(String::from_utf8_lossy(&output.stdout).to_string())
} else {
Err(FpgadSoftenerError::DfxMgr(format!(
"dfx-mgr-client failed.\n{}\nStdout:\n{:?}\nStderr:\n{:?}",
output.status,
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
)))
}
}

/// Extract the firmware-name property from a device tree overlay (.dtbo) file
///
Expand Down
Loading