csaf2cusa/src/lib.rs
Jia Chao 4e40d33b25 fix: regex cpature NVR
Signed-off-by: Jia Chao <jiac13@chinaunicom.cn>
2024-07-25 14:25:09 +08:00

342 lines
9.5 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

use std::collections::HashMap;
use std::fs;
use std::io::Write;
use std::path::{Path, PathBuf};
use lazy_static::lazy_static;
use regex::Regex;
use tracing::{debug, error, info, trace};
use tracing_subscriber::{fmt, EnvFilter};
use config::RepairConfig;
use csaf_parser::CSAF;
pub mod cli;
mod config;
/// 定义 crate::Error
/// 大部分函数返回的错误
pub type Error = Box<dyn std::error::Error + Send + Sync>;
/// 定义 crate::Result
pub type Result<T> = std::result::Result<T, Error>;
lazy_static! {
pub static ref NVR_RE: Regex = Regex::new(
// %{name}-%{version}-%{release}.[oexxxx.xxx.rpm]
r"^([a-zA-Z0-9\-_+]+)-([0-9a-zA-Z\._+]+)-([0-9a-zA-Z\._-]+).(oe[0-9a-z]+.[0-9a-z]+.rpm)"
)
.unwrap();
}
pub fn cumain() -> Result<()> {
set_up_logging()?;
let cli = cli::parse();
match cli.subcommand {
cli::CliSub::Convert(cli) => covert(&cli),
cli::CliSub::Db(cli) => sadb(&cli),
cli::CliSub::Auto(_) => auto(),
}
}
fn set_up_logging() -> crate::Result<()> {
fmt()
.with_ansi(false)
.with_timer(fmt::time::OffsetTime::local_rfc_3339().unwrap())
.with_line_number(true)
.with_env_filter(EnvFilter::from_default_env())
.try_init()
}
/// 可使用 convert 函数将 csaf 格式文件转换并输出至指定的 cusa 文件。
///
/// 例:
///
/// ```no_run
/// use csaf2cusa::cli::ConvertCli
///
/// let cli = ConvertCli::new(
/// // input
/// "xxx-csaf.json".to_string,
/// // output
/// Some("pkg_version_said.json".to_string),
/// // print to screen
/// false,
/// );
///
/// csaf2cusa::Convert(cli)?;
/// ```
pub fn covert(cli: &cli::ConvertCli) -> Result<()> {
// 检查 input此为必须项
let input = Path::new(&cli.input);
if !input.is_file() {
return Err("输入的文件不是有效的文件路径!".into());
}
// 从 input 读取 csaf并转换为 cusa
let csaf = CSAF::from(&cli.input)?;
let cusa = csaf.sainfo();
let data = serde_json::to_string_pretty(&cusa)?;
// 是否将 cusa 输出至指定文件
if let Some(output) = &cli.output {
let output = Path::new(output);
// output 所在父路径须提前创建
let parent = output.parent().unwrap();
if !parent.exists() {
return Err("无法访问输出文件所在路径,请确认!".into());
}
// 写入文件
let mut json_file = fs::OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(output)?;
json_file.write(data.as_bytes())?;
json_file.flush()?;
// 在输出到文件模式下,默认不再标准输出打印 CUSA
if !cli.print {
return Ok(());
}
}
// 在标准输出打印 CUSA
println!("{}", data);
Ok(())
}
pub fn sadb(cli: &cli::SaDbCli) -> Result<()> {
let source = &cli.from;
// 只要文件
let files = walk_dir(source, true);
let mut sadb = HashMap::new();
let mut cvedb = HashMap::new();
for _file in files {
match _file.extension() {
Some(tail) => {
if tail != "json" {
continue;
}
// 从 json 中读取 csaf 并转换为 cusa
let file = if let Some(file) = _file.to_str() {
file
} else {
continue;
};
let csaf = CSAF::from(file)?;
let sa = csaf.sainfo();
// cvedb
sa.cves.clone().into_iter().for_each(|cve| {
cvedb.insert(cve.id.clone(), cve);
});
// sadb
if let Some(_) = sadb.insert(sa.id.clone(), sa) {
// unimplemented!();
}
}
_ => {}
}
}
// 写入 sadb 文件
let data = serde_json::to_string_pretty(&sadb)?;
let mut sadb_file = fs::OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(&cli.sa)?;
sadb_file.write(data.as_bytes())?;
sadb_file.flush()?;
// 写入 cvedb 文件
let data = serde_json::to_string_pretty(&cvedb)?;
let mut cvedb_file = fs::OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(&cli.cve)?;
cvedb_file.write(data.as_bytes())?;
cvedb_file.flush()?;
Ok(())
}
// 递归去读一个路径,返回一个文件列表
#[allow(dead_code)]
fn walk_dir<P: AsRef<Path>>(path: P, nodir: bool) -> Vec<PathBuf> {
let mut res = vec![];
for entry in std::fs::read_dir(path).expect("read_dir call failed!") {
let entry = entry.unwrap().path();
if entry.is_dir() {
if !nodir {
res.push(entry.clone());
}
res.append(&mut walk_dir(entry, nodir));
} else {
// 暂不处理链接等情况
res.push(entry);
}
}
res
}
/// 从配置文件中读取 csaf 和 cusa 数据库的路径,并执行自动转换操作
pub fn auto() -> Result<()> {
// 默认配置为,但也可读取执行命令路径下的配置
let config = {
let default = Path::new("/etc/cuvars/csaf2cusa.json");
if default.is_file() {
default.to_str().unwrap()
} else {
"csaf2cusa.json"
}
};
info!("Config file is: {}", config);
let auto = config::AutoConfig::from(config)?;
info!("Load AutoConfig: {:?}", auto);
let files = walk_dir(auto.source(), true);
for _file in files {
match _file.extension() {
Some(tail) => {
if tail != "json" {
continue;
}
// 从 json 中读取 csaf 并转换为 cusa
let file = if let Some(file) = _file.to_str() {
file
} else {
continue;
};
trace!("parsing {}", file);
let csaf = CSAF::from(file)?;
_save_2_cusa_db(auto.target(), &csaf)?;
}
_ => {}
}
}
Ok(())
}
fn _save_2_cusa_db(dbpath: &str, csaf: &CSAF) -> Result<()> {
let mut db = PathBuf::from(dbpath);
let component = csaf.affected_component();
trace!("Get affected_component: {}", component);
// 这里随便取一个 src 包名
let _src = csaf.fixed_srcs()[0].product_id.as_str();
// TODO: may empty
if _src == "" {
error!(
"{}: ProductTree->packages[\"src\"] have empty productid, ignore it!",
csaf.id()
);
return Ok(());
}
db.push(_src.chars().next().unwrap().to_string());
// TODO!()
// db: "cusas/l/log4j,mybatis,netty,springframework,wildfly-security-manager,wildfly-elytron,wildfly-build-tools,wildfly-common,wildfly-core,thrift,json-lib,datanucleus-core,jgroups,mx4j,jboss-logging,infinispan,datanucleus-rdbms,avalon-logkit,datanucleus-api-jdo,avalon-framework,HikariCP,metrics"
// Error: Os { code: 63, kind: InvalidFilename, message: "File name too long" }
db.push(component);
// 读取修复配置
let mut repairconf = db.clone();
repairconf.push("config.json");
let repairconfig = match config::RepairConfig::from(&repairconf) {
Ok(repairconfig) => repairconfig,
_ => {
// 这里临时处理下,不应该创建
// TO BE DELETE
let parent = repairconf.parent().unwrap();
match fs::create_dir_all(parent) {
Ok(_) => {}
Err(e) => {
error!("sa_id: {}, {}", csaf.id(), e.to_string());
return Ok(());
}
}
let repairconfig = RepairConfig::new("22.03-LTS".to_string(), true, "".to_string());
// 写入默认配置
let data = serde_json::to_string_pretty(&repairconfig)?;
let mut repair = fs::OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(repairconf)?;
repair.write(data.as_bytes())?;
repair.flush()?;
repairconfig
}
};
// 是否影响当前软件包,即作上游判断
let mut not_affected = true;
for product in csaf.affected_products() {
if product.product_id.ends_with(repairconfig.upstream()) {
not_affected = false;
}
}
if not_affected {
debug!(
"{} not effected {}'s {}, skip...",
csaf.id(),
repairconfig.upstream(),
component
);
return Ok(());
}
// 增加 sa 信息
let mut nvr = String::new();
for product in &csaf.fixed_srcs() {
if product
.product_identification_helper
.cpe
.ends_with(repairconfig.upstream())
{
nvr = if let Some(caps) = NVR_RE.captures(&product.product_id) {
format!("{}-{}-{}", &caps[1], &caps[2], &caps[3])
} else {
product.product_id.clone()
};
nvr.push_str(&format!("_{}.json", csaf.id()));
break;
}
}
db.push(&nvr);
if db.exists() {
debug!("{} is already converted!", csaf.id());
return Ok(());
}
let data = serde_json::to_string_pretty(&csaf.sainfo())?;
let mut sa_file = fs::OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(db)?;
sa_file.write(data.as_bytes())?;
sa_file.flush()?;
Ok(())
}