agama/rust/agama-manager/src/ipmi.rs
2026-04-14 12:23:21 +02:00

130 lines
3.7 KiB
Rust

// Copyright (c) [2026] SUSE LLC
//
// All Rights Reserved.
//
// This program is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the Free
// Software Foundation; either version 2 of the License, or (at your option)
// any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, contact SUSE LLC.
//
// To contact SUSE LLC about this file by physical or electronic mail, you may
// find current contact information at www.suse.com.
use std::fs;
use std::io::Write;
use std::path::{Path, PathBuf};
use std::process::Command;
use tempfile::NamedTempFile;
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("ipmitool not found")]
Tool { desc: String },
#[error("ipmitool command call failed")]
Command { desc: String },
}
pub struct Ipmi {
tool: PathBuf,
device: PathBuf,
}
impl Ipmi {
const IPMI_STARTED: u8 = 0x07;
const IPMI_FINISHED: u8 = 0x08;
const IPMI_ABORTED: u8 = 0x09;
const IPMI_FAILED: u8 = 0x0A;
pub fn new(dev: &Path, tool: &Path) -> Self {
let instance = Self {
tool: tool.to_path_buf(),
device: dev.to_path_buf(),
};
tracing::info!("IPMI available: {}", instance.is_available());
instance
}
pub fn started(&self) -> Result<(), Error> {
self.send_command(Self::IPMI_STARTED)
}
pub fn finished(&self) -> Result<(), Error> {
self.send_command(Self::IPMI_FINISHED)
}
pub fn aborted(&self) -> Result<(), Error> {
self.send_command(Self::IPMI_ABORTED)
}
pub fn failed(&self) -> Result<(), Error> {
self.send_command(Self::IPMI_FAILED)
}
fn is_available(&self) -> bool {
fs::metadata(&self.device).is_ok() && fs::metadata(&self.tool).is_ok()
}
fn send_command(&self, code: u8) -> Result<(), Error> {
if !self.is_available() {
tracing::info!("IPMI not available");
return Ok(());
}
// Create a temporary file that is automatically deleted
let mut file = match NamedTempFile::new() {
Ok(f) => f,
Err(e) => {
return Err(Error::Tool {
desc: format!("ipmitool failed, cannot create temporary event file. {}", e),
});
}
};
// Write the event string
let content = format!("0x04 0x1F 0x00 0x6f 0x{:02x} 0x00 0x00\n", code);
if file.write_all(content.as_bytes()).is_err() {
return Err(Error::Tool {
desc: "ipmitool failed, cannot create event file".to_string(),
});
}
// Execute ipmitool
let status = Command::new(&self.tool)
.arg("event")
.arg("file")
.arg(file.path())
.status();
match status {
Ok(s) => {
if !s.success() {
Err(Error::Command {
desc: format!("ipmitool failed, exit code: {:?}", s.code()),
})
} else {
Ok(())
}
}
Err(e) => Err(Error::Command {
desc: format!("ipmitool failed, status: {}", e),
}),
}
}
}
impl Default for Ipmi {
fn default() -> Self {
Self::new(Path::new("/dev/ipmi0"), Path::new("/usr/bin/ipmitool"))
}
}