Compare commits
	
		
			No commits in common. "d36c574590339d7f850283b92536fb73354d8bfd" and "bfca9cf16398a997fd06b8b637090b2fff905bd6" have entirely different histories.
		
	
	
		
			d36c574590
			...
			bfca9cf163
		
	
		
							
								
								
									
										5
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										5
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							@ -105,7 +105,7 @@ dependencies = [
 | 
				
			|||||||
 "futures-core",
 | 
					 "futures-core",
 | 
				
			||||||
 "futures-util",
 | 
					 "futures-util",
 | 
				
			||||||
 "mio 1.0.4",
 | 
					 "mio 1.0.4",
 | 
				
			||||||
 "socket2 0.5.10",
 | 
					 "socket2",
 | 
				
			||||||
 "tokio",
 | 
					 "tokio",
 | 
				
			||||||
 "tracing",
 | 
					 "tracing",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
@ -167,7 +167,7 @@ dependencies = [
 | 
				
			|||||||
 "serde_json",
 | 
					 "serde_json",
 | 
				
			||||||
 "serde_urlencoded",
 | 
					 "serde_urlencoded",
 | 
				
			||||||
 "smallvec",
 | 
					 "smallvec",
 | 
				
			||||||
 "socket2 0.5.10",
 | 
					 "socket2",
 | 
				
			||||||
 "time",
 | 
					 "time",
 | 
				
			||||||
 "tracing",
 | 
					 "tracing",
 | 
				
			||||||
 "url",
 | 
					 "url",
 | 
				
			||||||
@ -2178,6 +2178,7 @@ dependencies = [
 | 
				
			|||||||
 "serde",
 | 
					 "serde",
 | 
				
			||||||
 "serde_json",
 | 
					 "serde_json",
 | 
				
			||||||
 "sysinfo",
 | 
					 "sysinfo",
 | 
				
			||||||
 | 
					 "uuid",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
 | 
				
			|||||||
@ -10,3 +10,4 @@ serde.workspace = true
 | 
				
			|||||||
serde_json.workspace = true
 | 
					serde_json.workspace = true
 | 
				
			||||||
log.workspace = true
 | 
					log.workspace = true
 | 
				
			||||||
env_logger.workspace = true
 | 
					env_logger.workspace = true
 | 
				
			||||||
 | 
					uuid.workspace = true
 | 
				
			||||||
 | 
				
			|||||||
@ -1,4 +1,3 @@
 | 
				
			|||||||
use log::debug;
 | 
					 | 
				
			||||||
use serde::{Deserialize, Serialize};
 | 
					use serde::{Deserialize, Serialize};
 | 
				
			||||||
use serde_json::Value;
 | 
					use serde_json::Value;
 | 
				
			||||||
use std::fs;
 | 
					use std::fs;
 | 
				
			||||||
@ -84,108 +83,27 @@ pub struct ManagementInterface {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl PhysicalHost {
 | 
					impl PhysicalHost {
 | 
				
			||||||
    pub fn gather() -> Result<Self, String> {
 | 
					    pub fn gather() -> Self {
 | 
				
			||||||
        let mut sys = System::new_all();
 | 
					        let mut sys = System::new_all();
 | 
				
			||||||
        sys.refresh_all();
 | 
					        sys.refresh_all();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Self::all_tools_available()?;
 | 
					        Self {
 | 
				
			||||||
 | 
					            storage_drives: Self::gather_storage_drives(),
 | 
				
			||||||
        Ok(Self {
 | 
					            storage_controller: Self::gather_storage_controller(),
 | 
				
			||||||
            storage_drives: Self::gather_storage_drives()?,
 | 
					            memory_modules: Self::gather_memory_modules(),
 | 
				
			||||||
            storage_controller: Self::gather_storage_controller()?,
 | 
					            cpus: Self::gather_cpus(&sys),
 | 
				
			||||||
            memory_modules: Self::gather_memory_modules()?,
 | 
					            chipset: Self::gather_chipset(),
 | 
				
			||||||
            cpus: Self::gather_cpus(&sys)?,
 | 
					            network_interfaces: Self::gather_network_interfaces(),
 | 
				
			||||||
            chipset: Self::gather_chipset()?,
 | 
					            management_interface: Self::gather_management_interface(),
 | 
				
			||||||
            network_interfaces: Self::gather_network_interfaces()?,
 | 
					            host_uuid: Self::get_host_uuid(),
 | 
				
			||||||
            management_interface: Self::gather_management_interface()?,
 | 
					 | 
				
			||||||
            host_uuid: Self::get_host_uuid()?,
 | 
					 | 
				
			||||||
        })
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    fn all_tools_available() -> Result<(), String> {
 | 
					 | 
				
			||||||
        let required_tools = [
 | 
					 | 
				
			||||||
            ("lsblk", "--version"),
 | 
					 | 
				
			||||||
            ("lspci", "--version"),
 | 
					 | 
				
			||||||
            ("lsmod", "--version"),
 | 
					 | 
				
			||||||
            ("dmidecode", "--version"),
 | 
					 | 
				
			||||||
            ("smartctl", "--version"),
 | 
					 | 
				
			||||||
            ("ip", "route"), // No version flag available
 | 
					 | 
				
			||||||
        ];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        let mut missing_tools = Vec::new();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        for (tool, tool_arg) in required_tools.iter() {
 | 
					 | 
				
			||||||
            // First check if tool exists in PATH using which(1)
 | 
					 | 
				
			||||||
            let exists = if let Ok(output) = Command::new("which").arg(tool).output() {
 | 
					 | 
				
			||||||
                output.status.success()
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                // Fallback: manual PATH search if which(1) is unavailable
 | 
					 | 
				
			||||||
                if let Ok(path_var) = std::env::var("PATH") {
 | 
					 | 
				
			||||||
                    path_var.split(':').any(|dir| {
 | 
					 | 
				
			||||||
                        let tool_path = std::path::Path::new(dir).join(tool);
 | 
					 | 
				
			||||||
                        tool_path.exists() && Self::is_executable(&tool_path)
 | 
					 | 
				
			||||||
                    })
 | 
					 | 
				
			||||||
                } else {
 | 
					 | 
				
			||||||
                    false
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if !exists {
 | 
					 | 
				
			||||||
                missing_tools.push(*tool);
 | 
					 | 
				
			||||||
                continue;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            // Verify tool is functional by checking version/help output
 | 
					 | 
				
			||||||
            let mut cmd = Command::new(tool);
 | 
					 | 
				
			||||||
            cmd.arg(tool_arg);
 | 
					 | 
				
			||||||
            cmd.stdout(std::process::Stdio::null());
 | 
					 | 
				
			||||||
            cmd.stderr(std::process::Stdio::null());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if let Ok(status) = cmd.status() {
 | 
					 | 
				
			||||||
                if !status.success() {
 | 
					 | 
				
			||||||
                    missing_tools.push(*tool);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                missing_tools.push(*tool);
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if !missing_tools.is_empty() {
 | 
					    fn gather_storage_drives() -> Vec<StorageDrive> {
 | 
				
			||||||
            let missing_str = missing_tools
 | 
					 | 
				
			||||||
                .iter()
 | 
					 | 
				
			||||||
                .map(|s| s.to_string())
 | 
					 | 
				
			||||||
                .collect::<Vec<String>>()
 | 
					 | 
				
			||||||
                .join(", ");
 | 
					 | 
				
			||||||
            return Err(format!(
 | 
					 | 
				
			||||||
                "The following required tools are not available: {}. Please install these tools to use PhysicalHost::gather()",
 | 
					 | 
				
			||||||
                missing_str
 | 
					 | 
				
			||||||
            ));
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        Ok(())
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    #[cfg(unix)]
 | 
					 | 
				
			||||||
    fn is_executable(path: &std::path::Path) -> bool {
 | 
					 | 
				
			||||||
        use std::os::unix::fs::PermissionsExt;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        match std::fs::metadata(path) {
 | 
					 | 
				
			||||||
            Ok(meta) => meta.permissions().mode() & 0o111 != 0,
 | 
					 | 
				
			||||||
            Err(_) => false,
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    #[cfg(not(unix))]
 | 
					 | 
				
			||||||
    fn is_executable(_path: &std::path::Path) -> bool {
 | 
					 | 
				
			||||||
        // On non-Unix systems, we assume existence implies executability
 | 
					 | 
				
			||||||
        true
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    fn gather_storage_drives() -> Result<Vec<StorageDrive>, String> {
 | 
					 | 
				
			||||||
        let mut drives = Vec::new();
 | 
					        let mut drives = Vec::new();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Use lsblk with JSON output for robust parsing
 | 
					        // Use lsblk with JSON output for robust parsing
 | 
				
			||||||
        let output = Command::new("lsblk")
 | 
					        if let Ok(output) = Command::new("lsblk")
 | 
				
			||||||
            .args([
 | 
					            .args([
 | 
				
			||||||
                "-d",
 | 
					                "-d",
 | 
				
			||||||
                "-o",
 | 
					                "-o",
 | 
				
			||||||
@ -196,30 +114,16 @@ impl PhysicalHost {
 | 
				
			|||||||
                "--json",
 | 
					                "--json",
 | 
				
			||||||
            ])
 | 
					            ])
 | 
				
			||||||
            .output()
 | 
					            .output()
 | 
				
			||||||
            .map_err(|e| format!("Failed to execute lsblk: {}", e))?;
 | 
					            && output.status.success()
 | 
				
			||||||
 | 
					            && let Ok(json) = serde_json::from_slice::<Value>(&output.stdout)
 | 
				
			||||||
        if !output.status.success() {
 | 
					            && let Some(blockdevices) = json.get("blockdevices").and_then(|v| v.as_array())
 | 
				
			||||||
            return Err(format!(
 | 
					        {
 | 
				
			||||||
                "lsblk command failed: {}",
 | 
					 | 
				
			||||||
                String::from_utf8_lossy(&output.stderr)
 | 
					 | 
				
			||||||
            ));
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        let json: Value = serde_json::from_slice(&output.stdout)
 | 
					 | 
				
			||||||
            .map_err(|e| format!("Failed to parse lsblk JSON output: {}", e))?;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        let blockdevices = json
 | 
					 | 
				
			||||||
            .get("blockdevices")
 | 
					 | 
				
			||||||
            .and_then(|v| v.as_array())
 | 
					 | 
				
			||||||
            .ok_or("Invalid lsblk JSON: missing 'blockdevices' array")?;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            for device in blockdevices {
 | 
					            for device in blockdevices {
 | 
				
			||||||
                let name = device
 | 
					                let name = device
 | 
				
			||||||
                    .get("name")
 | 
					                    .get("name")
 | 
				
			||||||
                    .and_then(|v| v.as_str())
 | 
					                    .and_then(|v| v.as_str())
 | 
				
			||||||
                .ok_or("Missing 'name' in lsblk device")?
 | 
					                    .unwrap_or("")
 | 
				
			||||||
                    .to_string();
 | 
					                    .to_string();
 | 
				
			||||||
 | 
					 | 
				
			||||||
                if name.is_empty() {
 | 
					                if name.is_empty() {
 | 
				
			||||||
                    continue;
 | 
					                    continue;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
@ -236,16 +140,13 @@ impl PhysicalHost {
 | 
				
			|||||||
                    .map(|s| s.trim().to_string())
 | 
					                    .map(|s| s.trim().to_string())
 | 
				
			||||||
                    .unwrap_or_default();
 | 
					                    .unwrap_or_default();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            let size_str = device
 | 
					                let size_str = device.get("size").and_then(|v| v.as_str()).unwrap_or("0");
 | 
				
			||||||
                .get("size")
 | 
					                let size_bytes = Self::parse_size(size_str).unwrap_or(0);
 | 
				
			||||||
                .and_then(|v| v.as_str())
 | 
					 | 
				
			||||||
                .ok_or("Missing 'size' in lsblk device")?;
 | 
					 | 
				
			||||||
            let size_bytes = Self::parse_size(size_str)?;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                let rotational = device
 | 
					                let rotational = device
 | 
				
			||||||
                    .get("rota")
 | 
					                    .get("rota")
 | 
				
			||||||
                    .and_then(|v| v.as_bool())
 | 
					                    .and_then(|v| v.as_bool())
 | 
				
			||||||
                .ok_or("Missing 'rota' in lsblk device")?;
 | 
					                    .unwrap_or(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                let wwn = device
 | 
					                let wwn = device
 | 
				
			||||||
                    .get("wwn")
 | 
					                    .get("wwn")
 | 
				
			||||||
@ -255,67 +156,56 @@ impl PhysicalHost {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                let device_path = Path::new("/sys/block").join(&name);
 | 
					                let device_path = Path::new("/sys/block").join(&name);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            let logical_block_size = Self::read_sysfs_u32(
 | 
					 | 
				
			||||||
                &device_path.join("queue/logical_block_size"),
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
            .map_err(|e| format!("Failed to read logical block size for {}: {}", name, e))?;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            let physical_block_size = Self::read_sysfs_u32(
 | 
					 | 
				
			||||||
                &device_path.join("queue/physical_block_size"),
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
            .map_err(|e| format!("Failed to read physical block size for {}: {}", name, e))?;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            let interface_type = Self::get_interface_type(&name, &device_path)?;
 | 
					 | 
				
			||||||
            let smart_status = Self::get_smart_status(&name)?;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                let mut drive = StorageDrive {
 | 
					                let mut drive = StorageDrive {
 | 
				
			||||||
                    name: name.clone(),
 | 
					                    name: name.clone(),
 | 
				
			||||||
                    model,
 | 
					                    model,
 | 
				
			||||||
                    serial,
 | 
					                    serial,
 | 
				
			||||||
                    size_bytes,
 | 
					                    size_bytes,
 | 
				
			||||||
                logical_block_size,
 | 
					                    logical_block_size: Self::read_sysfs_u32(
 | 
				
			||||||
                physical_block_size,
 | 
					                        &device_path.join("queue/logical_block_size"),
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                    .unwrap_or(512),
 | 
				
			||||||
 | 
					                    physical_block_size: Self::read_sysfs_u32(
 | 
				
			||||||
 | 
					                        &device_path.join("queue/physical_block_size"),
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                    .unwrap_or(512),
 | 
				
			||||||
                    rotational,
 | 
					                    rotational,
 | 
				
			||||||
                    wwn,
 | 
					                    wwn,
 | 
				
			||||||
                interface_type,
 | 
					                    interface_type: Self::get_interface_type(&name, &device_path),
 | 
				
			||||||
                smart_status,
 | 
					                    smart_status: Self::get_smart_status(&name),
 | 
				
			||||||
                };
 | 
					                };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                // Enhance with additional sysfs info if available
 | 
					                // Enhance with additional sysfs info if available
 | 
				
			||||||
                if device_path.exists() {
 | 
					                if device_path.exists() {
 | 
				
			||||||
                    if drive.model.is_empty() {
 | 
					                    if drive.model.is_empty() {
 | 
				
			||||||
                    drive.model = Self::read_sysfs_string(&device_path.join("device/model"))
 | 
					                        drive.model = Self::read_sysfs_string(&device_path.join("device/model"));
 | 
				
			||||||
                        .map_err(|e| format!("Failed to read model for {}: {}", name, e))?;
 | 
					 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                    if drive.serial.is_empty() {
 | 
					                    if drive.serial.is_empty() {
 | 
				
			||||||
                    drive.serial = Self::read_sysfs_string(&device_path.join("device/serial"))
 | 
					                        drive.serial = Self::read_sysfs_string(&device_path.join("device/serial"));
 | 
				
			||||||
                        .map_err(|e| format!("Failed to read serial for {}: {}", name, e))?;
 | 
					 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                drives.push(drive);
 | 
					                drives.push(drive);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					 | 
				
			||||||
        Ok(drives)
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn gather_storage_controller() -> Result<StorageController, String> {
 | 
					        drives
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn gather_storage_controller() -> StorageController {
 | 
				
			||||||
        let mut controller = StorageController {
 | 
					        let mut controller = StorageController {
 | 
				
			||||||
            name: "Unknown".to_string(),
 | 
					            name: "Unknown".to_string(),
 | 
				
			||||||
            driver: "Unknown".to_string(),
 | 
					            driver: "Unknown".to_string(),
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Use lspci with JSON output if available
 | 
					        // Use lspci with JSON output if available
 | 
				
			||||||
        let output = Command::new("lspci")
 | 
					        if let Ok(output) = Command::new("lspci")
 | 
				
			||||||
            .args(["-nn", "-d", "::0100", "-J"]) // Storage controllers class with JSON
 | 
					            .args(["-nn", "-d", "::0100", "-J"]) // Storage controllers class with JSON
 | 
				
			||||||
            .output()
 | 
					            .output()
 | 
				
			||||||
            .map_err(|e| format!("Failed to execute lspci: {}", e))?;
 | 
					            && output.status.success()
 | 
				
			||||||
 | 
					            && let Ok(json) = serde_json::from_slice::<Value>(&output.stdout)
 | 
				
			||||||
        if output.status.success() {
 | 
					            && let Some(devices) = json.as_array()
 | 
				
			||||||
            let json: Value = serde_json::from_slice(&output.stdout)
 | 
					        {
 | 
				
			||||||
                .map_err(|e| format!("Failed to parse lspci JSON output: {}", e))?;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if let Some(devices) = json.as_array() {
 | 
					 | 
				
			||||||
            for device in devices {
 | 
					            for device in devices {
 | 
				
			||||||
                if let Some(device_info) = device.as_object()
 | 
					                if let Some(device_info) = device.as_object()
 | 
				
			||||||
                    && let Some(name) = device_info
 | 
					                    && let Some(name) = device_info
 | 
				
			||||||
@ -329,16 +219,14 @@ impl PhysicalHost {
 | 
				
			|||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Fallback to text output if JSON fails or no device found
 | 
					        // Fallback to text output if JSON fails
 | 
				
			||||||
        if controller.name == "Unknown" {
 | 
					        if controller.name == "Unknown"
 | 
				
			||||||
            let output = Command::new("lspci")
 | 
					            && let Ok(output) = Command::new("lspci")
 | 
				
			||||||
                .args(["-nn", "-d", "::0100"]) // Storage controllers class
 | 
					                .args(["-nn", "-d", "::0100"]) // Storage controllers class
 | 
				
			||||||
                .output()
 | 
					                .output()
 | 
				
			||||||
                .map_err(|e| format!("Failed to execute lspci (fallback): {}", e))?;
 | 
					            && output.status.success()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
            if output.status.success() {
 | 
					 | 
				
			||||||
            let output_str = String::from_utf8_lossy(&output.stdout);
 | 
					            let output_str = String::from_utf8_lossy(&output.stdout);
 | 
				
			||||||
            if let Some(line) = output_str.lines().next() {
 | 
					            if let Some(line) = output_str.lines().next() {
 | 
				
			||||||
                let parts: Vec<&str> = line.split(':').collect();
 | 
					                let parts: Vec<&str> = line.split(':').collect();
 | 
				
			||||||
@ -347,14 +235,11 @@ impl PhysicalHost {
 | 
				
			|||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Try to get driver info from lsmod
 | 
					        // Try to get driver info from lsmod
 | 
				
			||||||
        let output = Command::new("lsmod")
 | 
					        if let Ok(output) = Command::new("lsmod").output()
 | 
				
			||||||
            .output()
 | 
					            && output.status.success()
 | 
				
			||||||
            .map_err(|e| format!("Failed to execute lsmod: {}", e))?;
 | 
					        {
 | 
				
			||||||
 | 
					 | 
				
			||||||
        if output.status.success() {
 | 
					 | 
				
			||||||
            let output_str = String::from_utf8_lossy(&output.stdout);
 | 
					            let output_str = String::from_utf8_lossy(&output.stdout);
 | 
				
			||||||
            for line in output_str.lines() {
 | 
					            for line in output_str.lines() {
 | 
				
			||||||
                if line.contains("ahci")
 | 
					                if line.contains("ahci")
 | 
				
			||||||
@ -371,28 +256,16 @@ impl PhysicalHost {
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Ok(controller)
 | 
					        controller
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn gather_memory_modules() -> Result<Vec<MemoryModule>, String> {
 | 
					    fn gather_memory_modules() -> Vec<MemoryModule> {
 | 
				
			||||||
        let mut modules = Vec::new();
 | 
					        let mut modules = Vec::new();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let output = Command::new("dmidecode")
 | 
					        if let Ok(output) = Command::new("dmidecode").arg("--type").arg("17").output()
 | 
				
			||||||
            .arg("--type")
 | 
					            && output.status.success()
 | 
				
			||||||
            .arg("17")
 | 
					        {
 | 
				
			||||||
            .output()
 | 
					            let output_str = String::from_utf8_lossy(&output.stdout);
 | 
				
			||||||
            .map_err(|e| format!("Failed to execute dmidecode: {}", e))?;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if !output.status.success() {
 | 
					 | 
				
			||||||
            return Err(format!(
 | 
					 | 
				
			||||||
                "dmidecode command failed: {}",
 | 
					 | 
				
			||||||
                String::from_utf8_lossy(&output.stderr)
 | 
					 | 
				
			||||||
            ));
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        let output_str = String::from_utf8(output.stdout)
 | 
					 | 
				
			||||||
            .map_err(|e| format!("Failed to parse dmidecode output: {}", e))?;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            let sections: Vec<&str> = output_str.split("Memory Device").collect();
 | 
					            let sections: Vec<&str> = output_str.split("Memory Device").collect();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            for section in sections.into_iter().skip(1) {
 | 
					            for section in sections.into_iter().skip(1) {
 | 
				
			||||||
@ -438,11 +311,12 @@ impl PhysicalHost {
 | 
				
			|||||||
                    modules.push(module);
 | 
					                    modules.push(module);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					 | 
				
			||||||
        Ok(modules)
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn gather_cpus(sys: &System) -> Result<Vec<CPU>, String> {
 | 
					        modules
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn gather_cpus(sys: &System) -> Vec<CPU> {
 | 
				
			||||||
        let mut cpus = Vec::new();
 | 
					        let mut cpus = Vec::new();
 | 
				
			||||||
        let global_cpu = sys.global_cpu_info();
 | 
					        let global_cpu = sys.global_cpu_info();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -454,29 +328,23 @@ impl PhysicalHost {
 | 
				
			|||||||
            frequency_mhz: global_cpu.frequency(),
 | 
					            frequency_mhz: global_cpu.frequency(),
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Ok(cpus)
 | 
					        cpus
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn gather_chipset() -> Result<Chipset, String> {
 | 
					    fn gather_chipset() -> Chipset {
 | 
				
			||||||
        Ok(Chipset {
 | 
					        Chipset {
 | 
				
			||||||
            name: Self::read_dmi("baseboard-product-name")?,
 | 
					            name: Self::read_dmi("board-product-name").unwrap_or_else(|| "Unknown".to_string()),
 | 
				
			||||||
            vendor: Self::read_dmi("baseboard-manufacturer")?,
 | 
					            vendor: Self::read_dmi("board-manufacturer").unwrap_or_else(|| "Unknown".to_string()),
 | 
				
			||||||
        })
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn gather_network_interfaces() -> Result<Vec<NetworkInterface>, String> {
 | 
					    fn gather_network_interfaces() -> Vec<NetworkInterface> {
 | 
				
			||||||
        let mut interfaces = Vec::new();
 | 
					        let mut interfaces = Vec::new();
 | 
				
			||||||
        let sys_net_path = Path::new("/sys/class/net");
 | 
					        let sys_net_path = Path::new("/sys/class/net");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let entries = fs::read_dir(sys_net_path)
 | 
					        if let Ok(entries) = fs::read_dir(sys_net_path) {
 | 
				
			||||||
            .map_err(|e| format!("Failed to read /sys/class/net: {}", e))?;
 | 
					            for entry in entries.flatten() {
 | 
				
			||||||
 | 
					                let iface_name = entry.file_name().into_string().unwrap_or_default();
 | 
				
			||||||
        for entry in entries {
 | 
					 | 
				
			||||||
            let entry = entry.map_err(|e| format!("Failed to read directory entry: {}", e))?;
 | 
					 | 
				
			||||||
            let iface_name = entry
 | 
					 | 
				
			||||||
                .file_name()
 | 
					 | 
				
			||||||
                .into_string()
 | 
					 | 
				
			||||||
                .map_err(|_| "Invalid UTF-8 in interface name")?;
 | 
					 | 
				
			||||||
                let iface_path = entry.path();
 | 
					                let iface_path = entry.path();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                // Skip virtual interfaces
 | 
					                // Skip virtual interfaces
 | 
				
			||||||
@ -496,42 +364,16 @@ impl PhysicalHost {
 | 
				
			|||||||
                    continue;
 | 
					                    continue;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            let mac_address = Self::read_sysfs_string(&iface_path.join("address"))
 | 
					                let mac_address = Self::read_sysfs_string(&iface_path.join("address"));
 | 
				
			||||||
                .map_err(|e| format!("Failed to read MAC address for {}: {}", iface_name, e))?;
 | 
					                let speed_mbps = Self::read_sysfs_u32(&iface_path.join("speed"));
 | 
				
			||||||
 | 
					                let operstate = Self::read_sysfs_string(&iface_path.join("operstate"));
 | 
				
			||||||
            let speed_mbps = if iface_path.join("speed").exists() {
 | 
					                let mtu = Self::read_sysfs_u32(&iface_path.join("mtu")).unwrap_or(1500);
 | 
				
			||||||
                match Self::read_sysfs_u32(&iface_path.join("speed")) {
 | 
					                let driver = Self::read_sysfs_string(&iface_path.join("device/driver/module"));
 | 
				
			||||||
                    Ok(speed) => Some(speed),
 | 
					                let firmware_version =
 | 
				
			||||||
                    Err(e) => {
 | 
					                    Self::read_sysfs_opt_string(&iface_path.join("device/firmware_version"));
 | 
				
			||||||
                        debug!(
 | 
					 | 
				
			||||||
                            "Failed to read speed for {}: {} . This is expected to fail on wifi interfaces.",
 | 
					 | 
				
			||||||
                            iface_name, e
 | 
					 | 
				
			||||||
                        );
 | 
					 | 
				
			||||||
                        None
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                None
 | 
					 | 
				
			||||||
            };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            let operstate = Self::read_sysfs_string(&iface_path.join("operstate"))
 | 
					 | 
				
			||||||
                .map_err(|e| format!("Failed to read operstate for {}: {}", iface_name, e))?;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            let mtu = Self::read_sysfs_u32(&iface_path.join("mtu"))
 | 
					 | 
				
			||||||
                .map_err(|e| format!("Failed to read MTU for {}: {}", iface_name, e))?;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            let driver =
 | 
					 | 
				
			||||||
                Self::read_sysfs_symlink_basename(&iface_path.join("device/driver/module"))
 | 
					 | 
				
			||||||
                    .map_err(|e| format!("Failed to read driver for {}: {}", iface_name, e))?;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            let firmware_version = Self::read_sysfs_opt_string(
 | 
					 | 
				
			||||||
                &iface_path.join("device/firmware_version"),
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
            .map_err(|e| format!("Failed to read firmware version for {}: {}", iface_name, e))?;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                // Get IP addresses using ip command with JSON output
 | 
					                // Get IP addresses using ip command with JSON output
 | 
				
			||||||
            let (ipv4_addresses, ipv6_addresses) = Self::get_interface_ips_json(&iface_name)
 | 
					                let (ipv4_addresses, ipv6_addresses) = Self::get_interface_ips_json(&iface_name);
 | 
				
			||||||
                .map_err(|e| format!("Failed to get IP addresses for {}: {}", iface_name, e))?;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                interfaces.push(NetworkInterface {
 | 
					                interfaces.push(NetworkInterface {
 | 
				
			||||||
                    name: iface_name,
 | 
					                    name: iface_name,
 | 
				
			||||||
@ -545,219 +387,142 @@ impl PhysicalHost {
 | 
				
			|||||||
                    firmware_version,
 | 
					                    firmware_version,
 | 
				
			||||||
                });
 | 
					                });
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					 | 
				
			||||||
        Ok(interfaces)
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn gather_management_interface() -> Result<Option<ManagementInterface>, String> {
 | 
					        interfaces
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn gather_management_interface() -> Option<ManagementInterface> {
 | 
				
			||||||
 | 
					        // Try to detect common management interfaces
 | 
				
			||||||
        if Path::new("/dev/ipmi0").exists() {
 | 
					        if Path::new("/dev/ipmi0").exists() {
 | 
				
			||||||
            Ok(Some(ManagementInterface {
 | 
					            Some(ManagementInterface {
 | 
				
			||||||
                kind: "IPMI".to_string(),
 | 
					                kind: "IPMI".to_string(),
 | 
				
			||||||
                address: None,
 | 
					                address: None,
 | 
				
			||||||
                firmware: Some(Self::read_dmi("bios-version")?),
 | 
					                firmware: Self::read_dmi("bios-version"),
 | 
				
			||||||
            }))
 | 
					            })
 | 
				
			||||||
        } else if Path::new("/sys/class/misc/mei").exists() {
 | 
					        } else if Path::new("/sys/class/misc/mei").exists() {
 | 
				
			||||||
            Ok(Some(ManagementInterface {
 | 
					            Some(ManagementInterface {
 | 
				
			||||||
                kind: "Intel ME".to_string(),
 | 
					                kind: "Intel ME".to_string(),
 | 
				
			||||||
                address: None,
 | 
					                address: None,
 | 
				
			||||||
                firmware: None,
 | 
					                firmware: None,
 | 
				
			||||||
            }))
 | 
					            })
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            Ok(None)
 | 
					            None
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn get_host_uuid() -> Result<String, String> {
 | 
					    fn get_host_uuid() -> String {
 | 
				
			||||||
        Self::read_dmi("system-uuid")
 | 
					        Self::read_dmi("system-uuid").unwrap()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Helper methods
 | 
					    // Helper methods
 | 
				
			||||||
    fn read_sysfs_string(path: &Path) -> Result<String, String> {
 | 
					    fn read_sysfs_string(path: &Path) -> String {
 | 
				
			||||||
        fs::read_to_string(path)
 | 
					        fs::read_to_string(path)
 | 
				
			||||||
            .map(|s| s.trim().to_string())
 | 
					            .unwrap_or_default()
 | 
				
			||||||
            .map_err(|e| format!("Failed to read {}: {}", path.display(), e))
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    fn read_sysfs_opt_string(path: &Path) -> Result<Option<String>, String> {
 | 
					 | 
				
			||||||
        match fs::read_to_string(path) {
 | 
					 | 
				
			||||||
            Ok(s) => {
 | 
					 | 
				
			||||||
                let s = s.trim().to_string();
 | 
					 | 
				
			||||||
                Ok(if s.is_empty() { None } else { Some(s) })
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(None),
 | 
					 | 
				
			||||||
            Err(e) => Err(format!("Failed to read {}: {}", path.display(), e)),
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    fn read_sysfs_u32(path: &Path) -> Result<u32, String> {
 | 
					 | 
				
			||||||
        fs::read_to_string(path)
 | 
					 | 
				
			||||||
            .map_err(|e| format!("Failed to read {}: {}", path.display(), e))?
 | 
					 | 
				
			||||||
            .trim()
 | 
					            .trim()
 | 
				
			||||||
            .parse()
 | 
					            .to_string()
 | 
				
			||||||
            .map_err(|e| format!("Failed to parse {}: {}", path.display(), e))
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn read_sysfs_symlink_basename(path: &Path) -> Result<String, String> {
 | 
					    fn read_sysfs_opt_string(path: &Path) -> Option<String> {
 | 
				
			||||||
        match fs::read_link(path) {
 | 
					        fs::read_to_string(path)
 | 
				
			||||||
            Ok(target_path) => match target_path.file_name() {
 | 
					            .ok()
 | 
				
			||||||
                Some(name_osstr) => match name_osstr.to_str() {
 | 
					            .map(|s| s.trim().to_string())
 | 
				
			||||||
                    Some(name_str) => Ok(name_str.to_string()),
 | 
					            .filter(|s| !s.is_empty())
 | 
				
			||||||
                    None => Err(format!(
 | 
					 | 
				
			||||||
                        "Symlink target basename is not valid UTF-8: {}",
 | 
					 | 
				
			||||||
                        target_path.display()
 | 
					 | 
				
			||||||
                    )),
 | 
					 | 
				
			||||||
                },
 | 
					 | 
				
			||||||
                None => Err(format!(
 | 
					 | 
				
			||||||
                    "Symlink target has no basename: {} -> {}",
 | 
					 | 
				
			||||||
                    path.display(),
 | 
					 | 
				
			||||||
                    target_path.display()
 | 
					 | 
				
			||||||
                )),
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
            Err(e) if e.kind() == std::io::ErrorKind::NotFound => Err(format!(
 | 
					 | 
				
			||||||
                "Could not resolve symlink for path : {}",
 | 
					 | 
				
			||||||
                path.display()
 | 
					 | 
				
			||||||
            )),
 | 
					 | 
				
			||||||
            Err(e) => Err(format!("Failed to read symlink {}: {}", path.display(), e)),
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn read_dmi(field: &str) -> Result<String, String> {
 | 
					    fn read_sysfs_u32(path: &Path) -> Option<u32> {
 | 
				
			||||||
        let output = Command::new("dmidecode")
 | 
					        fs::read_to_string(path)
 | 
				
			||||||
 | 
					            .ok()
 | 
				
			||||||
 | 
					            .and_then(|s| s.trim().parse().ok())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn read_dmi(field: &str) -> Option<String> {
 | 
				
			||||||
 | 
					        Command::new("dmidecode")
 | 
				
			||||||
            .arg("-s")
 | 
					            .arg("-s")
 | 
				
			||||||
            .arg(field)
 | 
					            .arg(field)
 | 
				
			||||||
            .output()
 | 
					            .output()
 | 
				
			||||||
            .map_err(|e| format!("Failed to execute dmidecode for field {}: {}", field, e))?;
 | 
					            .ok()
 | 
				
			||||||
 | 
					            .filter(|output| output.status.success())
 | 
				
			||||||
        if !output.status.success() {
 | 
					            .and_then(|output| String::from_utf8(output.stdout).ok())
 | 
				
			||||||
            return Err(format!(
 | 
					 | 
				
			||||||
                "dmidecode command failed for field {}: {}",
 | 
					 | 
				
			||||||
                field,
 | 
					 | 
				
			||||||
                String::from_utf8_lossy(&output.stderr)
 | 
					 | 
				
			||||||
            ));
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        String::from_utf8(output.stdout)
 | 
					 | 
				
			||||||
            .map(|s| s.trim().to_string())
 | 
					            .map(|s| s.trim().to_string())
 | 
				
			||||||
            .map_err(|e| {
 | 
					            .filter(|s| !s.is_empty())
 | 
				
			||||||
                format!(
 | 
					 | 
				
			||||||
                    "Failed to parse dmidecode output for field {}: {}",
 | 
					 | 
				
			||||||
                    field, e
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
            })
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn get_interface_type(device_name: &str, device_path: &Path) -> Result<String, String> {
 | 
					    fn get_interface_type(device_name: &str, device_path: &Path) -> String {
 | 
				
			||||||
        if device_name.starts_with("nvme") {
 | 
					        if device_name.starts_with("nvme") {
 | 
				
			||||||
            Ok("NVMe".to_string())
 | 
					            "NVMe".to_string()
 | 
				
			||||||
        } else if device_name.starts_with("sd") {
 | 
					        } else if device_name.starts_with("sd") {
 | 
				
			||||||
            Ok("SATA".to_string())
 | 
					            "SATA".to_string()
 | 
				
			||||||
        } else if device_name.starts_with("hd") {
 | 
					        } else if device_name.starts_with("hd") {
 | 
				
			||||||
            Ok("IDE".to_string())
 | 
					            "IDE".to_string()
 | 
				
			||||||
        } else if device_name.starts_with("vd") {
 | 
					        } else if device_name.starts_with("vd") {
 | 
				
			||||||
            Ok("VirtIO".to_string())
 | 
					            "VirtIO".to_string()
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            // Try to determine from device path
 | 
					            // Try to determine from device path
 | 
				
			||||||
            let subsystem = Self::read_sysfs_string(&device_path.join("device/subsystem"))?;
 | 
					            Self::read_sysfs_string(&device_path.join("device/subsystem"))
 | 
				
			||||||
            Ok(subsystem
 | 
					 | 
				
			||||||
                .split('/')
 | 
					                .split('/')
 | 
				
			||||||
                .next_back()
 | 
					                .next_back()
 | 
				
			||||||
                .unwrap_or("Unknown")
 | 
					                .unwrap_or("Unknown")
 | 
				
			||||||
                .to_string())
 | 
					                .to_string()
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn get_smart_status(device_name: &str) -> Result<Option<String>, String> {
 | 
					    fn get_smart_status(device_name: &str) -> Option<String> {
 | 
				
			||||||
        let output = Command::new("smartctl")
 | 
					        Command::new("smartctl")
 | 
				
			||||||
            .arg("-H")
 | 
					            .arg("-H")
 | 
				
			||||||
            .arg(format!("/dev/{}", device_name))
 | 
					            .arg(format!("/dev/{}", device_name))
 | 
				
			||||||
            .output()
 | 
					            .output()
 | 
				
			||||||
            .map_err(|e| format!("Failed to execute smartctl for {}: {}", device_name, e))?;
 | 
					            .ok()
 | 
				
			||||||
 | 
					            .filter(|output| output.status.success())
 | 
				
			||||||
        if !output.status.success() {
 | 
					            .and_then(|output| String::from_utf8(output.stdout).ok())
 | 
				
			||||||
            return Ok(None);
 | 
					            .and_then(|s| {
 | 
				
			||||||
 | 
					                s.lines()
 | 
				
			||||||
 | 
					                    .find(|line| line.contains("SMART overall-health self-assessment"))
 | 
				
			||||||
 | 
					                    .and_then(|line| line.split(':').nth(1))
 | 
				
			||||||
 | 
					                    .map(|s| s.trim().to_string())
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let stdout = String::from_utf8(output.stdout)
 | 
					    fn parse_size(size_str: &str) -> Option<u64> {
 | 
				
			||||||
            .map_err(|e| format!("Failed to parse smartctl output for {}: {}", device_name, e))?;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        for line in stdout.lines() {
 | 
					 | 
				
			||||||
            if line.contains("SMART overall-health self-assessment") {
 | 
					 | 
				
			||||||
                if let Some(status) = line.split(':').nth(1) {
 | 
					 | 
				
			||||||
                    return Ok(Some(status.trim().to_string()));
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        Ok(None)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    fn parse_size(size_str: &str) -> Result<u64, String> {
 | 
					 | 
				
			||||||
        debug!("Parsing size_str '{size_str}'");
 | 
					 | 
				
			||||||
        let size;
 | 
					 | 
				
			||||||
        if size_str.ends_with('T') {
 | 
					        if size_str.ends_with('T') {
 | 
				
			||||||
            size = size_str[..size_str.len() - 1]
 | 
					            size_str[..size_str.len() - 1]
 | 
				
			||||||
                .parse::<f64>()
 | 
					                .parse::<u64>()
 | 
				
			||||||
                .map(|t| t * 1024.0 * 1024.0 * 1024.0 * 1024.0)
 | 
					                .ok()
 | 
				
			||||||
                .map_err(|e| format!("Failed to parse T size '{}': {}", size_str, e))
 | 
					                .map(|t| t * 1024 * 1024 * 1024 * 1024)
 | 
				
			||||||
        } else if size_str.ends_with('G') {
 | 
					        } else if size_str.ends_with('G') {
 | 
				
			||||||
            size = size_str[..size_str.len() - 1]
 | 
					            size_str[..size_str.len() - 1]
 | 
				
			||||||
                .parse::<f64>()
 | 
					                .parse::<u64>()
 | 
				
			||||||
                .map(|g| g * 1024.0 * 1024.0 * 1024.0)
 | 
					                .ok()
 | 
				
			||||||
                .map_err(|e| format!("Failed to parse G size '{}': {}", size_str, e))
 | 
					                .map(|g| g * 1024 * 1024 * 1024)
 | 
				
			||||||
        } else if size_str.ends_with('M') {
 | 
					        } else if size_str.ends_with('M') {
 | 
				
			||||||
            size = size_str[..size_str.len() - 1]
 | 
					            size_str[..size_str.len() - 1]
 | 
				
			||||||
                .parse::<f64>()
 | 
					                .parse::<u64>()
 | 
				
			||||||
                .map(|m| m * 1024.0 * 1024.0)
 | 
					                .ok()
 | 
				
			||||||
                .map_err(|e| format!("Failed to parse M size '{}': {}", size_str, e))
 | 
					                .map(|m| m * 1024 * 1024)
 | 
				
			||||||
        } else if size_str.ends_with('K') {
 | 
					        } else if size_str.ends_with('K') {
 | 
				
			||||||
            size = size_str[..size_str.len() - 1]
 | 
					            size_str[..size_str.len() - 1]
 | 
				
			||||||
                .parse::<f64>()
 | 
					                .parse::<u64>()
 | 
				
			||||||
                .map(|k| k * 1024.0)
 | 
					                .ok()
 | 
				
			||||||
                .map_err(|e| format!("Failed to parse K size '{}': {}", size_str, e))
 | 
					                .map(|k| k * 1024)
 | 
				
			||||||
        } else if size_str.ends_with('B') {
 | 
					        } else if size_str.ends_with('B') {
 | 
				
			||||||
            size = size_str[..size_str.len() - 1]
 | 
					            size_str[..size_str.len() - 1].parse::<u64>().ok()
 | 
				
			||||||
                .parse::<f64>()
 | 
					 | 
				
			||||||
                .map_err(|e| format!("Failed to parse B size '{}': {}", size_str, e))
 | 
					 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            size = size_str
 | 
					            size_str.parse::<u64>().ok()
 | 
				
			||||||
                .parse::<f64>()
 | 
					        }
 | 
				
			||||||
                .map_err(|e| format!("Failed to parse size '{}': {}", size_str, e))
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        size.map(|s| s as u64)
 | 
					    fn get_interface_ips_json(iface_name: &str) -> (Vec<String>, Vec<String>) {
 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    fn get_interface_ips_json(iface_name: &str) -> Result<(Vec<String>, Vec<String>), String> {
 | 
					 | 
				
			||||||
        let mut ipv4 = Vec::new();
 | 
					        let mut ipv4 = Vec::new();
 | 
				
			||||||
        let mut ipv6 = Vec::new();
 | 
					        let mut ipv6 = Vec::new();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Get IPv4 addresses using JSON output
 | 
					        // Get IPv4 addresses using JSON output
 | 
				
			||||||
        let output = Command::new("ip")
 | 
					        if let Ok(output) = Command::new("ip")
 | 
				
			||||||
            .args(["-j", "-4", "addr", "show", iface_name])
 | 
					            .args(["-j", "-4", "addr", "show", iface_name])
 | 
				
			||||||
            .output()
 | 
					            .output()
 | 
				
			||||||
            .map_err(|e| {
 | 
					            && output.status.success()
 | 
				
			||||||
                format!(
 | 
					            && let Ok(json) = serde_json::from_slice::<Value>(&output.stdout)
 | 
				
			||||||
                    "Failed to execute ip command for IPv4 on {}: {}",
 | 
					            && let Some(addrs) = json.as_array()
 | 
				
			||||||
                    iface_name, e
 | 
					        {
 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
            })?;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if !output.status.success() {
 | 
					 | 
				
			||||||
            return Err(format!(
 | 
					 | 
				
			||||||
                "ip command for IPv4 on {} failed: {}",
 | 
					 | 
				
			||||||
                iface_name,
 | 
					 | 
				
			||||||
                String::from_utf8_lossy(&output.stderr)
 | 
					 | 
				
			||||||
            ));
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        let json: Value = serde_json::from_slice(&output.stdout).map_err(|e| {
 | 
					 | 
				
			||||||
            format!(
 | 
					 | 
				
			||||||
                "Failed to parse ip JSON output for IPv4 on {}: {}",
 | 
					 | 
				
			||||||
                iface_name, e
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
        })?;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if let Some(addrs) = json.as_array() {
 | 
					 | 
				
			||||||
            for addr_info in addrs {
 | 
					            for addr_info in addrs {
 | 
				
			||||||
                if let Some(addr_info_obj) = addr_info.as_object()
 | 
					                if let Some(addr_info_obj) = addr_info.as_object()
 | 
				
			||||||
                    && let Some(addr_info) =
 | 
					                    && let Some(addr_info) =
 | 
				
			||||||
@ -775,32 +540,13 @@ impl PhysicalHost {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Get IPv6 addresses using JSON output
 | 
					        // Get IPv6 addresses using JSON output
 | 
				
			||||||
        let output = Command::new("ip")
 | 
					        if let Ok(output) = Command::new("ip")
 | 
				
			||||||
            .args(["-j", "-6", "addr", "show", iface_name])
 | 
					            .args(["-j", "-6", "addr", "show", iface_name])
 | 
				
			||||||
            .output()
 | 
					            .output()
 | 
				
			||||||
            .map_err(|e| {
 | 
					            && output.status.success()
 | 
				
			||||||
                format!(
 | 
					            && let Ok(json) = serde_json::from_slice::<Value>(&output.stdout)
 | 
				
			||||||
                    "Failed to execute ip command for IPv6 on {}: {}",
 | 
					            && let Some(addrs) = json.as_array()
 | 
				
			||||||
                    iface_name, e
 | 
					        {
 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
            })?;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if !output.status.success() {
 | 
					 | 
				
			||||||
            return Err(format!(
 | 
					 | 
				
			||||||
                "ip command for IPv6 on {} failed: {}",
 | 
					 | 
				
			||||||
                iface_name,
 | 
					 | 
				
			||||||
                String::from_utf8_lossy(&output.stderr)
 | 
					 | 
				
			||||||
            ));
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        let json: Value = serde_json::from_slice(&output.stdout).map_err(|e| {
 | 
					 | 
				
			||||||
            format!(
 | 
					 | 
				
			||||||
                "Failed to parse ip JSON output for IPv6 on {}: {}",
 | 
					 | 
				
			||||||
                iface_name, e
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
        })?;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if let Some(addrs) = json.as_array() {
 | 
					 | 
				
			||||||
            for addr_info in addrs {
 | 
					            for addr_info in addrs {
 | 
				
			||||||
                if let Some(addr_info_obj) = addr_info.as_object()
 | 
					                if let Some(addr_info_obj) = addr_info.as_object()
 | 
				
			||||||
                    && let Some(addr_info) =
 | 
					                    && let Some(addr_info) =
 | 
				
			||||||
@ -820,6 +566,6 @@ impl PhysicalHost {
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Ok((ipv4, ipv6))
 | 
					        (ipv4, ipv6)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -9,16 +9,8 @@ mod hwinfo;
 | 
				
			|||||||
async fn inventory() -> impl Responder {
 | 
					async fn inventory() -> impl Responder {
 | 
				
			||||||
    log::info!("Received inventory request");
 | 
					    log::info!("Received inventory request");
 | 
				
			||||||
    let host = PhysicalHost::gather();
 | 
					    let host = PhysicalHost::gather();
 | 
				
			||||||
    match host {
 | 
					 | 
				
			||||||
        Ok(host) => {
 | 
					 | 
				
			||||||
    log::info!("Inventory data gathered successfully");
 | 
					    log::info!("Inventory data gathered successfully");
 | 
				
			||||||
    actix_web::HttpResponse::Ok().json(host)
 | 
					    actix_web::HttpResponse::Ok().json(host)
 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        Err(error) => {
 | 
					 | 
				
			||||||
            log::error!("Inventory data gathering FAILED");
 | 
					 | 
				
			||||||
            actix_web::HttpResponse::InternalServerError().json(error)
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[actix_web::main]
 | 
					#[actix_web::main]
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user