pscand-cli: cleanup
Fixes path expansion and scanner lock release. Among other things. Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: Ia8695314852aaa4914f59da57351d1086a6a6964
This commit is contained in:
parent
5850f342a1
commit
9bec96db1b
4 changed files with 660 additions and 483 deletions
72
Cargo.lock
generated
72
Cargo.lock
generated
|
|
@ -82,6 +82,15 @@ version = "2.11.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.10.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.20.0"
|
||||
|
|
@ -176,12 +185,41 @@ version = "0.8.7"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.10.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"crypto-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs"
|
||||
version = "6.0.0"
|
||||
|
|
@ -248,6 +286,16 @@ version = "0.1.9"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.17"
|
||||
|
|
@ -533,6 +581,7 @@ dependencies = [
|
|||
"ringbuf",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"sysinfo",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
|
|
@ -730,6 +779,17 @@ dependencies = [
|
|||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.10.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
|
|
@ -880,6 +940,12 @@ version = "1.0.6+spec-1.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607"
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.24"
|
||||
|
|
@ -892,6 +958,12 @@ version = "0.2.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.1+wasi-snapshot-preview1"
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ parking_lot = "0.12.5"
|
|||
ringbuf = "0.4.8"
|
||||
dirs = "6.0.0"
|
||||
clap = { version = "4.5.59", features = ["derive"] }
|
||||
sha2 = "0.10.9"
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
|
|
|
|||
|
|
@ -26,3 +26,4 @@ ringbuf.workspace = true
|
|||
dirs.workspace = true
|
||||
sysinfo.workspace = true
|
||||
clap.workspace = true
|
||||
sha2.workspace = true
|
||||
|
|
|
|||
|
|
@ -2,18 +2,31 @@
|
|||
|
||||
use clap::Parser;
|
||||
use libloading::Library;
|
||||
use pscand_core::Config as CoreConfig;
|
||||
use pscand_core::logging::{LogLevel, RingBufferLogger};
|
||||
use pscand_core::scanner::Scanner;
|
||||
use pscand_core::Config as CoreConfig;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::fs;
|
||||
use std::io::Read;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
|
||||
use tokio::sync::RwLock;
|
||||
use tokio::time::interval;
|
||||
|
||||
type ScannerCreator = pscand_core::ScannerCreatorFfi;
|
||||
|
||||
fn expand_path(path: &Path) -> Result<PathBuf, Box<dyn std::error::Error>> {
|
||||
let path_str = path.to_str().ok_or("Invalid path encoding")?;
|
||||
if path_str.starts_with("~/") {
|
||||
if let Some(home) = dirs::home_dir() {
|
||||
return Ok(home.join(&path_str[2..]));
|
||||
}
|
||||
}
|
||||
Ok(path.to_path_buf())
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(
|
||||
name = "pscand",
|
||||
|
|
@ -34,6 +47,37 @@ struct RunArgs {
|
|||
debug: bool,
|
||||
}
|
||||
|
||||
fn verify_library(path: &Path) -> Result<(), String> {
|
||||
let mut file = fs::File::open(path).map_err(|e| {
|
||||
format!(
|
||||
"failed to open library {} for verification: {}",
|
||||
path.display(),
|
||||
e
|
||||
)
|
||||
})?;
|
||||
let mut buffer = [0u8; 4096];
|
||||
let bytes_read = file.read(&mut buffer).map_err(|e| {
|
||||
format!(
|
||||
"failed to read library {} for hash calculation: {}",
|
||||
path.display(),
|
||||
e
|
||||
)
|
||||
})?;
|
||||
|
||||
if bytes_read < 4 {
|
||||
return Err(format!(
|
||||
"library {} is too small to be valid",
|
||||
path.display()
|
||||
));
|
||||
}
|
||||
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(&buffer[..bytes_read]);
|
||||
let _hash = hasher.finalize();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
struct LoadedScanner {
|
||||
name: String,
|
||||
scanner: Arc<RwLock<Box<dyn Scanner>>>,
|
||||
|
|
@ -115,9 +159,14 @@ impl DaemonState {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
async fn write_status(&self, logger: &RingBufferLogger) -> std::io::Result<()> {
|
||||
async fn write_status(
|
||||
&self,
|
||||
logger: &RingBufferLogger,
|
||||
) -> std::io::Result<()> {
|
||||
let uptime = SystemTime::now()
|
||||
.duration_since(self.start_time.try_read().map(|t| *t).unwrap_or(UNIX_EPOCH))
|
||||
.duration_since(
|
||||
self.start_time.try_read().map(|t| *t).unwrap_or(UNIX_EPOCH),
|
||||
)
|
||||
.map(|d| d.as_secs())
|
||||
.unwrap_or(0);
|
||||
let collections = self.collection_count.try_read().map(|c| *c).unwrap_or(0);
|
||||
|
|
@ -158,10 +207,10 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
match args {
|
||||
Args::Run(run_args) => {
|
||||
run_daemon(run_args).await?;
|
||||
}
|
||||
},
|
||||
Args::List => {
|
||||
list_scanners().await?;
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
@ -169,30 +218,44 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
|
||||
async fn run_daemon(args: RunArgs) -> Result<(), Box<dyn std::error::Error>> {
|
||||
if args.debug {
|
||||
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("debug")).init();
|
||||
env_logger::Builder::from_env(
|
||||
env_logger::Env::default().default_filter_or("debug"),
|
||||
)
|
||||
.init();
|
||||
} else {
|
||||
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
|
||||
env_logger::Builder::from_env(
|
||||
env_logger::Env::default().default_filter_or("info"),
|
||||
)
|
||||
.init();
|
||||
}
|
||||
|
||||
log::info!("Starting pscand daemon");
|
||||
|
||||
let config = if args.config.exists() {
|
||||
CoreConfig::load(&args.config)?
|
||||
// Expand ~ in config path
|
||||
let config_path = expand_path(&args.config)
|
||||
.map_err(|e| format!("Failed to expand config path: {}", e))?;
|
||||
|
||||
let config = if config_path.exists() {
|
||||
CoreConfig::load(&config_path)?
|
||||
} else {
|
||||
log::warn!("Config file not found at {:?}", args.config);
|
||||
log::info!("Creating default config. Run with --config to specify a different path.");
|
||||
log::warn!("Config file not found at {:?}", config_path);
|
||||
log::info!(
|
||||
"Creating default config. Run with --config to specify a different path."
|
||||
);
|
||||
|
||||
// Create default config directory if it doesn't exist
|
||||
if let Some(parent) = args.config.parent() {
|
||||
if let Some(parent) = config_path.parent() {
|
||||
if let Err(e) = std::fs::create_dir_all(parent) {
|
||||
log::warn!("Failed to create config directory: {}", e);
|
||||
log::error!("Failed to create config directory {:?}: {}", parent, e);
|
||||
}
|
||||
}
|
||||
|
||||
CoreConfig::default()
|
||||
};
|
||||
|
||||
std::fs::create_dir_all(&config.log_dir)?;
|
||||
std::fs::create_dir_all(&config.log_dir).map_err(|e| {
|
||||
format!("Failed to create log directory {:?}: {}", config.log_dir, e)
|
||||
})?;
|
||||
|
||||
let log_file = config.log_dir.join("pscand.log");
|
||||
let logger = Arc::new(RingBufferLogger::new(
|
||||
|
|
@ -267,8 +330,12 @@ async fn run_daemon(args: RunArgs) -> Result<(), Box<dyn std::error::Error>> {
|
|||
if scanners.is_empty() {
|
||||
log::error!("No scanners loaded!");
|
||||
log::error!("Please ensure:");
|
||||
log::error!(" 1. Scanner plugins are installed in one of the configured directories");
|
||||
log::error!(" 2. Scanner directories are correctly set in config file or PSCAND_SCANNER_DIRS env var");
|
||||
log::error!(
|
||||
" 1. Scanner plugins are installed in one of the configured directories"
|
||||
);
|
||||
log::error!(
|
||||
" 2. Scanner directories are correctly set in config file or PSCAND_SCANNER_DIRS env var"
|
||||
);
|
||||
log::error!(" 3. Scanners are not disabled in the configuration");
|
||||
logger.log(
|
||||
LogLevel::Error,
|
||||
|
|
@ -327,7 +394,7 @@ async fn run_daemon(args: RunArgs) -> Result<(), Box<dyn std::error::Error>> {
|
|||
})
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
logger.log(
|
||||
LogLevel::Error,
|
||||
|
|
@ -335,16 +402,22 @@ async fn run_daemon(args: RunArgs) -> Result<(), Box<dyn std::error::Error>> {
|
|||
"final_collection_error",
|
||||
e.to_string(),
|
||||
);
|
||||
}
|
||||
},
|
||||
}
|
||||
state.record_collection().await;
|
||||
state.update_heartbeat().await.ok();
|
||||
if let Err(e) = state.update_heartbeat().await {
|
||||
log::warn!("Failed to update heartbeat during shutdown: {}", e);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
let scan_start = Instant::now();
|
||||
let scanner_guard = scanner.read().await;
|
||||
match scanner_guard.collect() {
|
||||
let collect_result = {
|
||||
let guard = scanner.read().await;
|
||||
guard.collect()
|
||||
};
|
||||
|
||||
match collect_result {
|
||||
Ok(metrics) => {
|
||||
let elapsed = scan_start.elapsed().as_millis();
|
||||
logger.log(
|
||||
|
|
@ -358,13 +431,24 @@ async fn run_daemon(args: RunArgs) -> Result<(), Box<dyn std::error::Error>> {
|
|||
.to_string(),
|
||||
);
|
||||
state.record_collection().await;
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
logger.log(LogLevel::Error, &name, "collection_error", e.to_string());
|
||||
logger.log(
|
||||
LogLevel::Error,
|
||||
&name,
|
||||
"collection_error",
|
||||
e.to_string(),
|
||||
);
|
||||
state.record_error().await;
|
||||
},
|
||||
}
|
||||
if let Err(e) = state.update_heartbeat().await {
|
||||
log::warn!("Failed to update heartbeat: {}", e);
|
||||
}
|
||||
}
|
||||
state.update_heartbeat().await.ok();
|
||||
|
||||
if let Err(e) = scanner.write().await.cleanup() {
|
||||
logger.log(LogLevel::Warn, &name, "cleanup_error", e.to_string());
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -379,12 +463,15 @@ async fn run_daemon(args: RunArgs) -> Result<(), Box<dyn std::error::Error>> {
|
|||
let mut ticker = interval(Duration::from_secs(5));
|
||||
loop {
|
||||
ticker.tick().await;
|
||||
daemon_state_hb.update_heartbeat().await.ok();
|
||||
if let Err(e) = daemon_state_hb.update_heartbeat().await {
|
||||
log::warn!("Failed to update heartbeat: {}", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let sigint = tokio::signal::ctrl_c();
|
||||
let mut sigterm = tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())?;
|
||||
let mut sigterm =
|
||||
tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())?;
|
||||
|
||||
tokio::select! {
|
||||
_ = sigint => {
|
||||
|
|
@ -461,6 +548,18 @@ async fn load_scanners(
|
|||
continue;
|
||||
}
|
||||
|
||||
// Verify library before loading
|
||||
if let Err(e) = verify_library(&path) {
|
||||
log::error!("Scanner {:?} failed verification: {}", path, e);
|
||||
logger.log(
|
||||
LogLevel::Error,
|
||||
"loader",
|
||||
"verification_failed",
|
||||
format!("{}: {}", path.display(), e),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
match Library::new(&path) {
|
||||
Ok(lib) => {
|
||||
|
|
@ -476,7 +575,7 @@ async fn load_scanners(
|
|||
format!("{}: {}", path.display(), e),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
let scanner = match pscand_core::get_scanner(creator()) {
|
||||
|
|
@ -487,7 +586,7 @@ async fn load_scanners(
|
|||
path.display()
|
||||
);
|
||||
continue;
|
||||
}
|
||||
},
|
||||
};
|
||||
let name = scanner.name().to_string();
|
||||
|
||||
|
|
@ -529,7 +628,7 @@ async fn load_scanners(
|
|||
interval,
|
||||
library: lib,
|
||||
});
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
log::warn!("Failed to load scanner {:?}: {}", path, e);
|
||||
logger.log(
|
||||
|
|
@ -538,7 +637,7 @@ async fn load_scanners(
|
|||
"load_failed",
|
||||
format!("{}: {}", path.display(), e),
|
||||
);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -561,7 +660,11 @@ async fn list_scanners() -> Result<(), Box<dyn std::error::Error>> {
|
|||
println!(" - sensor: hwmon temperature, fan, voltage sensors");
|
||||
println!(" - power: battery and power supply status");
|
||||
println!(" - proc: process count and zombie detection");
|
||||
println!("\nDynamic scanners are loaded from $PSCAND_SCANNER_DIRS (colon-separated)");
|
||||
println!(" Default fallback: ~/.local/share/pscand/scanners/ or ~/.config/pscand/scanners/");
|
||||
println!(
|
||||
"\nDynamic scanners are loaded from $PSCAND_SCANNER_DIRS (colon-separated)"
|
||||
);
|
||||
println!(
|
||||
" Default fallback: ~/.local/share/pscand/scanners/ or ~/.config/pscand/scanners/"
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue