From 866b881246da635eb271382582c8eb63c78f2f89 Mon Sep 17 00:00:00 2001 From: chenshiming2 Date: Tue, 22 Nov 2022 07:28:40 +0800 Subject: [PATCH] add rdnf --- rdnf/.gitignore | 1 + rdnf/.vscode/extensions.json | 5 + rdnf/.vscode/launch.json | 42 +++ rdnf/Cargo.toml | 39 +++ rdnf/README.md | 33 ++ rdnf/build.rs | 6 + rdnf/rpm-sys/Cargo.toml | 16 + rdnf/rpm-sys/build.rs | 50 +++ rdnf/rpm-sys/src/ffi.rs | 2 + rdnf/rpm-sys/src/lib.rs | 16 + rdnf/solv-sys/Cargo.toml | 19 ++ rdnf/solv-sys/README.md | 7 + rdnf/solv-sys/build.rs | 116 +++++++ rdnf/solv-sys/src/ffi.rs | 2 + rdnf/solv-sys/src/lib.rs | 11 + rdnf/src/c_lib/mod.rs | 162 ++++++++++ rdnf/src/c_lib/queue_static.c | 159 +++++++++ rdnf/src/c_lib/rpm_trans.c | 129 ++++++++ rdnf/src/cli.rs | 168 ++++++++++ rdnf/src/conf.rs | 204 ++++++++++++ rdnf/src/default.rs | 38 +++ rdnf/src/errors.rs | 166 ++++++++++ rdnf/src/goal.rs | 413 ++++++++++++++++++++++++ rdnf/src/gpgcheck.rs | 187 +++++++++++ rdnf/src/history.rs | 29 ++ rdnf/src/i18n.rs | 73 +++++ rdnf/src/lock.rs | 102 ++++++ rdnf/src/main.rs | 141 ++++++++ rdnf/src/metalink.rs | 171 ++++++++++ rdnf/src/output.rs | 149 +++++++++ rdnf/src/pkgutils.rs | 176 ++++++++++ rdnf/src/repomd.rs | 199 ++++++++++++ rdnf/src/rpm_trans.rs | 484 ++++++++++++++++++++++++++++ rdnf/src/solv/mod.rs | 25 ++ rdnf/src/solv/rdnf_pkg.rs | 441 +++++++++++++++++++++++++ rdnf/src/solv/rdnf_query.rs | 322 +++++++++++++++++++ rdnf/src/solv/rdnf_repo.rs | 211 ++++++++++++ rdnf/src/solv/sack.rs | 129 ++++++++ rdnf/src/sub_command/cache.rs | 127 ++++++++ rdnf/src/sub_command/info.rs | 143 +++++++++ rdnf/src/sub_command/install.rs | 430 +++++++++++++++++++++++++ rdnf/src/sub_command/mod.rs | 7 + rdnf/src/sub_command/repo.rs | 514 ++++++++++++++++++++++++++++++ rdnf/src/sub_command/repoutils.rs | 478 +++++++++++++++++++++++++++ rdnf/src/sub_command/search.rs | 144 +++++++++ rdnf/src/sub_command/update.rs | 296 +++++++++++++++++ rdnf/src/utils.rs | 76 +++++ 47 files changed, 6858 insertions(+) create mode 100644 rdnf/.gitignore create mode 100644 rdnf/.vscode/extensions.json create mode 100644 rdnf/.vscode/launch.json create mode 100644 rdnf/Cargo.toml create mode 100644 rdnf/README.md create mode 100644 rdnf/build.rs create mode 100644 rdnf/rpm-sys/Cargo.toml create mode 100644 rdnf/rpm-sys/build.rs create mode 100644 rdnf/rpm-sys/src/ffi.rs create mode 100644 rdnf/rpm-sys/src/lib.rs create mode 100644 rdnf/solv-sys/Cargo.toml create mode 100644 rdnf/solv-sys/README.md create mode 100644 rdnf/solv-sys/build.rs create mode 100644 rdnf/solv-sys/src/ffi.rs create mode 100644 rdnf/solv-sys/src/lib.rs create mode 100644 rdnf/src/c_lib/mod.rs create mode 100644 rdnf/src/c_lib/queue_static.c create mode 100644 rdnf/src/c_lib/rpm_trans.c create mode 100644 rdnf/src/cli.rs create mode 100644 rdnf/src/conf.rs create mode 100644 rdnf/src/default.rs create mode 100644 rdnf/src/errors.rs create mode 100644 rdnf/src/goal.rs create mode 100644 rdnf/src/gpgcheck.rs create mode 100644 rdnf/src/history.rs create mode 100644 rdnf/src/i18n.rs create mode 100644 rdnf/src/lock.rs create mode 100644 rdnf/src/main.rs create mode 100644 rdnf/src/metalink.rs create mode 100644 rdnf/src/output.rs create mode 100644 rdnf/src/pkgutils.rs create mode 100644 rdnf/src/repomd.rs create mode 100644 rdnf/src/rpm_trans.rs create mode 100644 rdnf/src/solv/mod.rs create mode 100644 rdnf/src/solv/rdnf_pkg.rs create mode 100644 rdnf/src/solv/rdnf_query.rs create mode 100644 rdnf/src/solv/rdnf_repo.rs create mode 100644 rdnf/src/solv/sack.rs create mode 100644 rdnf/src/sub_command/cache.rs create mode 100644 rdnf/src/sub_command/info.rs create mode 100644 rdnf/src/sub_command/install.rs create mode 100644 rdnf/src/sub_command/mod.rs create mode 100644 rdnf/src/sub_command/repo.rs create mode 100644 rdnf/src/sub_command/repoutils.rs create mode 100644 rdnf/src/sub_command/search.rs create mode 100644 rdnf/src/sub_command/update.rs create mode 100644 rdnf/src/utils.rs diff --git a/rdnf/.gitignore b/rdnf/.gitignore new file mode 100644 index 00000000..ea8c4bf7 --- /dev/null +++ b/rdnf/.gitignore @@ -0,0 +1 @@ +/target diff --git a/rdnf/.vscode/extensions.json b/rdnf/.vscode/extensions.json new file mode 100644 index 00000000..fe0411f3 --- /dev/null +++ b/rdnf/.vscode/extensions.json @@ -0,0 +1,5 @@ +{ + "recommendations": [ + "vadimcn.vscode-lldb" + ] +} \ No newline at end of file diff --git a/rdnf/.vscode/launch.json b/rdnf/.vscode/launch.json new file mode 100644 index 00000000..02e79d79 --- /dev/null +++ b/rdnf/.vscode/launch.json @@ -0,0 +1,42 @@ +{ + // 使用 IntelliSense 了解相关属性。 + // 悬停以查看现有属性的描述。 + // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + + { + "type": "lldb", + "request": "launch", + "name": "Cargo launch", + "cargo": { + "args": [ + "build", + "--lib" + ] + }, + "args": [] + }, + + { + "type": "lldb", + "request": "launch", + "name": "Debug executable 'rdnf'", + "cargo": { + "args": [ + "build", + "--bin=rdnf", + "--package=rdnf" + ], + "filter": { + "name": "rdnf", + "kind": "bin" + } + }, + // "args": ["install","rpm","rpm-devel","file:///meg-0.2.4-5.fc36.x86_64.rpm","http://mirrors.aliyun.com/fedora/releases/36/Everything/x86_64/os/Packages/i/ibacm-39.0-1.fc36.x86_64.rpm"], + "args": ["install","nodejs"], + // "args":["install","http://mirrors.aliyun.com/fedora/releases/36/Everything/x86_64/os/Packages/i/ibacm-39.0-1.fc36.x86_64.rpm"], + "cwd": "${workspaceFolder}", + }, + ] +} \ No newline at end of file diff --git a/rdnf/Cargo.toml b/rdnf/Cargo.toml new file mode 100644 index 00000000..4896f882 --- /dev/null +++ b/rdnf/Cargo.toml @@ -0,0 +1,39 @@ +[package] +name = "rdnf" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[features] +default=["zh_CN"] +en_US=[] +zh_CN=[] + + +[dependencies] +clap = {version="4",features = ["derive"]} +config = "0.13" +console="0.15" +glob="0.3.0" +indicatif = "0.17.0" +dialoguer = "0.10" +# libsolv-sys = "0.1.4" +rustix={version = "*",features = ["process"]} +anyhow = "1.0.60" +lazy_static = "1.4.0" +curl="0.4" +tokio={version = "1.20",features = ["rt","macros"]} +libc="0.2.119" +serde={version = "*",features = ["serde_derive"]} +rpm-sys={path="./rpm-sys",version="*"} +solv-sys={path="./solv-sys",version="*"} +quick-xml={version = "0.25"} +sha-1="*" +sha2="*" +md-5="*" +hex="*" +chrono="0.4" + + +[build-dependencies] +cc="1" \ No newline at end of file diff --git a/rdnf/README.md b/rdnf/README.md new file mode 100644 index 00000000..cffc3e17 --- /dev/null +++ b/rdnf/README.md @@ -0,0 +1,33 @@ +## 简介 ++ 此项目是*tdnf*的rust实现版,主要依托于*rpm-devel*,*libsolv-devel* ++ 目前已经实现了核心功能即 *repolist*,*makecache*,*search*,*install*,*reinstall*,*remove*,*update*,*info* ++ 此项目准确的说是rpm-devel和libsolv-devel的整合品。故含有大量的代码是rust对c的ffi绑定,具有一定参考价值。 ++ 项目局限性:tdnf主要是为了photon而生,而photon是轻量级容器环境,故软件包仓库的软件数量少,元数据信息少,tdnf的性能可以应付,但对于像fedora拥有丰富的软件包,tdnf解决软件包依赖冲突问题需要长达接近20s的时间。故本项目有进一步改进的空间。 + +### 项目解析 +1. 首先解析命令行参数 clap是rust生态主流的命令行编程框架,简单高效。 +2. 解析配置文件 */etc/dnf/dnf.conf*, 使用config crates包解析,映射成 rust对象。 +3. 读取 */etc/yum.repos.d/* 文件夹下所有.repo文件,读取各个软件仓库的数据, 映射成rust对象。 +4. 初始化libsolv,创建pool对象。 + +上述5个步骤,整合成Rdnf,形成全局上下文环境。 +#### repolist +此命令在于简单显示已经读取的repo仓库信息,可显示已启用或禁用的repo仓库。 +#### *makecache* +此命令至关重要,是整个项目的重要基石,即读取已启用的repo的仓库,下载镜像站的索引文件、软件包的元数据,基于libsolv生成.solv格式的缓存文件,若有缓存文件,跳过1,2,3。 +1. 首先下载 repomd.xml文件,有两种方式: + + 基于.repo配置文件,若有baseurl配置项,即baseurl+*repodata/repomd.xml* 即可得 repomd.xml的下载链接 + + 基于.repo配置文件,若无baseurl但有metalink配置项,下载metalink.xml文件,解析metalink.xml文件,可获得repomd.xml的下载链接。 +2. 解析 repomd.xml文件,含有 primary,filelists,other,updateinfo等xml文件的元数据,再基于baseurl即可得对应的下载链接,这些xml文件含有对应repo镜像仓库的软件包元数据,保存于/var/cache/rdnf/repodata文件夹下。 +3. 基于libsolv,将repo仓库的primary等xml文件生成.solv文件,保存于/var/cache/rdnf/solvcache/文件夹下 +4. 再将.solv加载到pool中,pool可以理解为是libsolv环境中的上下文环境 + +#### search +搜索软件包,基于makecache命令,将xml文件生成.solv,再加载到pool,利用pool和libsolv提供的接口可轻松搜索软件包的元数据信息。 + +#### install,remove,update,reinstall +这几个命令操作类似。 +主要思路为两大部分,先是基于libsolv解决软件包依赖冲突问题,再使用rpm-devel解决rpm软件包具体安装问题。 ++ 使用是prepare_all_pkgs, 初步解决是否已经安装,安装的是否是最新版本的问题,将已经初步处理过,需要进一步解决的软件包id放入queue_goal中,在goal中完成主要通过*solver_solve*解决软件包依赖冲突问题,生成安装或卸载列表,然后基于列表中的软件包id获取软件包的详细信息,例如软件包架构,版本,大小的信息。 ++ 生成rpmts,可以理解为rpm-devel环境中的上下文环境,根据上述的软件包列表,下载软件包,若有必要,需要解决gpgkey问题。根据需要设置rpm事务的参数flag,并设置rpm回调函数(由于c难以回调rust函数,故直接使用c实现),由rpm-devel解决具体的rpm安装事务,在执行过程中,回调之前设置的函数,实现单个软件包安装进度的打印显示。 + diff --git a/rdnf/build.rs b/rdnf/build.rs new file mode 100644 index 00000000..9c9d9713 --- /dev/null +++ b/rdnf/build.rs @@ -0,0 +1,6 @@ +fn main(){ + cc::Build::new() + .file("src/c_lib/queue_static.c") + .file("src/c_lib/rpm_trans.c") + .compile("queue_static"); +} \ No newline at end of file diff --git a/rdnf/rpm-sys/Cargo.toml b/rdnf/rpm-sys/Cargo.toml new file mode 100644 index 00000000..a0ee17ed --- /dev/null +++ b/rdnf/rpm-sys/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "rpm-sys" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +libc='0.2' + +[build-dependencies] +bindgen = "0.60.1" +cc = "1" +pkg-config = "0.3.25" +cmake = "0.1.48" +anyhow = "1" diff --git a/rdnf/rpm-sys/build.rs b/rdnf/rpm-sys/build.rs new file mode 100644 index 00000000..1e4e47d1 --- /dev/null +++ b/rdnf/rpm-sys/build.rs @@ -0,0 +1,50 @@ +use anyhow::Result; +use std::{fs, path::Path}; +const ALLOWED_FUNC_PREFIX: &[&str] = &[ + "arg", "header", "rpm", "F", "fd", "pgp", "ri", "rs", "rr", "rc", "rm", "url", +]; +const ALLOWED_TYPE_PREFIX: &[&str] = &[ + "ARG", + "Header", + "HEADER", + "header", + "rpm", + "poptContext", + "FD", + "off", + "pgp", + "DIGEST", + "fnpyKey", + "url", +]; +fn main() -> Result<()> { + let conf = pkg_config::Config::new(); + let lib = conf.probe("rpm")?; + for inc in lib.include_paths { + // println!("{:?}",inc); + if inc.join("rpm").is_dir() { + let include_path = inc.join("rpm"); + let output = std::env::var("OUT_DIR")?; + let mut builder = bindgen::Builder::default() + .header(include_path.join("argv.h").to_str().unwrap()) + .header(include_path.join("header.h").to_str().unwrap()) + .header(include_path.join("rpmtypes.h").to_str().unwrap()); + for inc in fs::read_dir(include_path)? { + let inc = inc?; + let name = inc.file_name(); + let name = name.to_string_lossy(); + if name.starts_with("rpm") && name.ends_with(".h") { + builder = builder.header(inc.path().to_str().unwrap()); + } + } + builder + .allowlist_type(format!("({}).*", ALLOWED_TYPE_PREFIX.join("|"))) + .allowlist_var(".*") + .allowlist_function(format!("({}).*", ALLOWED_FUNC_PREFIX.join("|"))) + .generate() + .unwrap() + .write_to_file(Path::new(&output).join("bindings.rs"))?; + } + } + Ok(()) +} diff --git a/rdnf/rpm-sys/src/ffi.rs b/rdnf/rpm-sys/src/ffi.rs new file mode 100644 index 00000000..66e09db5 --- /dev/null +++ b/rdnf/rpm-sys/src/ffi.rs @@ -0,0 +1,2 @@ +#![allow(warnings)] +include!(concat!(env!("OUT_DIR"), "/bindings.rs")); diff --git a/rdnf/rpm-sys/src/lib.rs b/rdnf/rpm-sys/src/lib.rs new file mode 100644 index 00000000..ff83b69d --- /dev/null +++ b/rdnf/rpm-sys/src/lib.rs @@ -0,0 +1,16 @@ +pub mod ffi; + +pub fn add(left: usize, right: usize) -> usize { + left + right +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn it_works() { + unsafe { + let rpmts = ffi::rpmtsCreate(); + } + } +} diff --git a/rdnf/solv-sys/Cargo.toml b/rdnf/solv-sys/Cargo.toml new file mode 100644 index 00000000..fab10d86 --- /dev/null +++ b/rdnf/solv-sys/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "solv-sys" +version = "0.1.4" +license = "MIT" +repository = "https://github.com/AOSC-Dev/abbs-meta-rs/tree/master/libsolv-sys" +description = "Raw bindings to libsolv" +keywords = ["libsolv", "solver", "dependency"] +authors = ["liushuyu "] +edition = "2018" + +[dependencies] +libc = "0.2" + +[build-dependencies] +cc = "1" +pkg-config = "0.3" +bindgen = "0.60" +anyhow = "1" +cmake = "0.1" diff --git a/rdnf/solv-sys/README.md b/rdnf/solv-sys/README.md new file mode 100644 index 00000000..26b2c438 --- /dev/null +++ b/rdnf/solv-sys/README.md @@ -0,0 +1,7 @@ +# `libsolv-sys` + +Low-level Rust binding for [`libsolv`](https://github.com/openSUSE/libsolv). + +The current binding is tailored to dpkg-based distro usage (Enabled `libsolvext` but only Debian support is added). + +However, the library will prefer to use the system `libsolv` if there is one. Make sure to install the development files from the package manager. diff --git a/rdnf/solv-sys/build.rs b/rdnf/solv-sys/build.rs new file mode 100644 index 00000000..b585fd9f --- /dev/null +++ b/rdnf/solv-sys/build.rs @@ -0,0 +1,116 @@ +use anyhow::{anyhow, Result}; +use std::{ + fs, + path::{self, Path, PathBuf}, +}; + +const ALLOWED_FUNC_PREFIX: &[&str] = &[ + "map", + "policy", + "pool", + "prune", + "queue", + "repo", + "repodata", + "selection", + "solv", + "solver", + "testcase", + "transaction", + "dataiterator", + "datamatcher", + "stringpool", +]; + +fn find_system_libsolv() -> Result { + let mut conf = pkg_config::Config::new(); + let lib = conf.atleast_version("0.7").probe("libsolv")?; + conf.atleast_version("0.7").probe("libsolvext")?; + + for inc in lib.include_paths { + if inc.join("solv").is_dir() { + return Ok(inc.join("solv")); + } + } + + Err(anyhow!("Error finding libsolv include path")) +} + +fn build_libsolv() -> Result { + println!("cargo:warning=System libsolv not found. Using bundled version."); + let p = path::PathBuf::from("./libsolv/CMakeLists.txt"); + if !p.is_file() { + return Err(anyhow!( + "Bundled libsolv not found, please do `git submodule update --init`." + )); + } + let out = cmake::Config::new(p.parent().unwrap()) + .define("ENABLE_DEBIAN", "ON") + .define("DEBIAN", "ON") + .define("ENABLE_STATIC", "ON") + .define("DISABLE_SHARED", "ON") + .target(&std::env::var("CMAKE_TARGET").unwrap_or_else(|_| std::env::var("TARGET").unwrap())) + .build(); + println!( + "cargo:rustc-link-search=native={}", + out.join("lib").display() + ); + println!( + "cargo:rustc-link-search=native={}", + out.join("lib64").display() + ); + println!("cargo:rustc-link-lib=static=solv"); + println!("cargo:rustc-link-lib=static=solvext"); + println!("cargo:rustc-link-lib=z"); + + Ok(out.join("include/solv")) +} + +fn check_solvext_bindings( + include_path: &Path, + builder: bindgen::Builder, +) -> Result { + let mut builder = builder; + for inc in fs::read_dir(include_path)? { + let inc = inc?; + let name = inc.file_name(); + let name = name.to_string_lossy(); + // all the solvext include files are named like `repo_.h` + if name.starts_with("repo_") && name.ends_with(".h") { + builder = builder.header(inc.path().to_str().unwrap()); + } + } + + Ok(builder) +} + +fn generate_bindings(include_path: &Path) -> Result<()> { + let output = std::env::var("OUT_DIR")?; + let generator = bindgen::Builder::default() + .header(include_path.join("solver.h").to_str().unwrap()) + .header(include_path.join("solverdebug.h").to_str().unwrap()) + .header(include_path.join("selection.h").to_str().unwrap()) + .header(include_path.join("knownid.h").to_str().unwrap()) + .header(include_path.join("chksum.h").to_str().unwrap()) + .header(include_path.join("solv_xfopen.h").to_str().unwrap()) + .header(include_path.join("evr.h").to_str().unwrap()) + .header(include_path.join("testcase.h").to_str().unwrap()) + .allowlist_type("(Id|solv_knownid)") + .allowlist_var(".*") + .allowlist_function(format!("({}).*", ALLOWED_FUNC_PREFIX.join("|"))); + check_solvext_bindings(include_path, generator)? + .generate() + .unwrap() + .write_to_file(Path::new(&output).join("bindings.rs"))?; + Ok(()) +} + +fn main() -> Result<()> { + let include_path = match find_system_libsolv() { + Ok(p) => p, + Err(_) => build_libsolv()?, + }; + generate_bindings(&include_path)?; + + Ok(()) +} diff --git a/rdnf/solv-sys/src/ffi.rs b/rdnf/solv-sys/src/ffi.rs new file mode 100644 index 00000000..66e09db5 --- /dev/null +++ b/rdnf/solv-sys/src/ffi.rs @@ -0,0 +1,2 @@ +#![allow(warnings)] +include!(concat!(env!("OUT_DIR"), "/bindings.rs")); diff --git a/rdnf/solv-sys/src/lib.rs b/rdnf/solv-sys/src/lib.rs new file mode 100644 index 00000000..32bebf2f --- /dev/null +++ b/rdnf/solv-sys/src/lib.rs @@ -0,0 +1,11 @@ +//! Low-level Rust binding for libsolv. +//! +//! The current binding is tailored to dpkg-based distro usage +//! (Enabled libsolvext but only Debian support is added). +//! +//! All the bindings are inside the `ffi` module. +//! Since this is a low-level binding library, you need to consult +//! [libsolv C API documentation](https://github.com/openSUSE/libsolv) for more information. + +/// FFI bindings for libsolv +pub mod ffi; diff --git a/rdnf/src/c_lib/mod.rs b/rdnf/src/c_lib/mod.rs new file mode 100644 index 00000000..12661aa0 --- /dev/null +++ b/rdnf/src/c_lib/mod.rs @@ -0,0 +1,162 @@ +use rpm_sys::ffi::rpmts; +use solv_sys::ffi::{Dataiterator, Map, Pool, Queue, Repo, Solvable}; + +extern "C" { + fn char_ptr_offset_bind(ptr: *const i8, offset: i32) -> *const i8; + fn queue_empty_static(q: *mut Queue); + fn queue_push_static(q: *mut Queue, id: i32); + fn queue_push2_static(q: *mut Queue, id1: i32, id2: i32); + fn create_data_iterator_empty_bind() -> Dataiterator; // Dataiterator di={0} + // fn create_Repo_empty_bind() -> s_Repo; // s_Pool p={0} + fn dataiterator_init_simple_bind( + di: *mut Dataiterator, + pool: *mut Pool, + match_: *const ::std::os::raw::c_char, + flags: ::std::os::raw::c_int, + ); + fn dataiterator_set_search_simple_bind(di: *mut Dataiterator); + fn get_queue_element_value_bind(q: *const Queue, index: ::std::os::raw::c_int) -> i32; + fn pool_id2solvable_static(pool: *const Pool, p: ::std::os::raw::c_int) -> *mut Solvable; + fn solv_add_flags_to_jobs_bind(q: *mut Queue, flags: ::std::os::raw::c_int); + fn get_pool_solvables_value_bind( + pool: *const Pool, + index: ::std::os::raw::c_uint, + ) -> *mut Solvable; + fn is_pseudo_package_static(pool: *mut Pool, s: *mut Solvable) -> i32; + fn pool_id2repo_static(pool: *const Pool, id: i32) -> *mut Repo; + fn pool_whatprovides_static(pool: *mut Pool, d: i32) -> i32; + fn get_pool_whatprovidesdata_value_bind(pool: *const Pool, index: i32) -> i32; + fn pool_match_nevr_static(pool: *mut Pool, s: *mut Solvable, d: i32) -> i32; + fn pool_disabled_solvable_static(pool: *const Pool, s: *mut Solvable) -> i32; + // fn map_empty_static(m: *mut Map); + fn map_set_static(m: *mut Map, n: i32); + fn map_setall_static(m: *mut Map); + // fn map_clr_static(m: *mut Map, n: i32); + // fn map_tst_static(m: *mut Map, n: i32); + // fn map_clr_at_static(m: *mut Map, n: i32); + // int set_callback_fn(rpmts ts,int quiet,int term_width){ + fn set_callback_fn(ts: rpmts, quiet: i32, term_width: u16) -> i32; + +} +// fn char_ptr_offset_bind(ptr:*const i8,offset:i32)->*const i8; +pub fn char_ptr_offset(ptr: *const i8, offset: i32) -> *const i8 { + unsafe { char_ptr_offset_bind(ptr, offset) } +} +#[inline] +pub fn queue_empty(q: *mut Queue) { + unsafe { + queue_empty_static(q); + } +} +#[inline] +pub fn queue_push(q: *mut Queue, id: i32) { + unsafe { + queue_push_static(q, id); + } +} +#[inline] +pub fn queue_push2(q: *mut Queue, id1: i32, id2: i32) { + unsafe { + queue_push2_static(q, id1, id2); + } +} +#[inline] +pub fn create_dataiterator_empty() -> Dataiterator { + unsafe { create_data_iterator_empty_bind() } +} +// #[inline] +// pub fn create_Repo_empty() -> s_Repo { +// unsafe { create_Repo_empty_bind() } +// } +#[inline] +pub fn dataiterator_init_simple( + di: *mut Dataiterator, + pool: *mut Pool, + match_: *const ::std::os::raw::c_char, + flags: ::std::os::raw::c_int, +) { + unsafe { + dataiterator_init_simple_bind(di, pool, match_, flags); + } +} +#[inline] +pub fn dataiterator_set_search_simple(di: *mut Dataiterator) { + unsafe { + dataiterator_set_search_simple_bind(di); + } +} +#[inline] +pub fn get_queue_element_value(q: *const Queue, index: u32) -> i32 { + unsafe { get_queue_element_value_bind(q, index as i32) } +} +#[inline] +pub fn pool_id2solvable(pool: *const Pool, p: i32) -> *mut Solvable { + unsafe { pool_id2solvable_static(pool, p) } +} +#[inline] +pub fn solv_add_flags_to_jobs(q: *mut Queue, flags: i32) { + unsafe { solv_add_flags_to_jobs_bind(q, flags) } +} +#[inline] +pub fn get_pool_solvables_value(pool: *const Pool, index: u32) -> *mut Solvable { + unsafe { get_pool_solvables_value_bind(pool, index) } +} +#[inline] +pub fn is_pseudo_package(pool: *mut Pool, s: *mut Solvable) -> bool { + unsafe { is_pseudo_package_static(pool, s) == 1 } +} +#[inline] +pub fn pool_id2repo(pool: *const Pool, id: i32) -> *mut Repo { + unsafe { pool_id2repo_static(pool, id) } +} +#[inline] +pub fn pool_whatprovides(pool: *mut Pool, id: i32) -> i32 { + unsafe { pool_whatprovides_static(pool, id) } +} +#[inline] +pub fn get_pool_whatprovidesdata_value(pool: *const Pool, index: i32) -> i32 { + unsafe { get_pool_whatprovidesdata_value_bind(pool, index) } +} +#[inline] +pub fn pool_match_nevr(pool: *mut Pool, s: *mut Solvable, d: i32) -> i32 { + unsafe { pool_match_nevr_static(pool, s, d) } +} + +pub fn pool_disabled_solvable(pool: *const Pool, s: *mut Solvable) -> bool { + let result = unsafe { pool_disabled_solvable_static(pool, s) }; + result == 1 +} +// pub fn map_empty(m: *mut Map) { +// unsafe { +// map_empty_static(m); +// } +// } + +pub fn map_set(m: *mut Map, n: i32) { + unsafe { + map_set_static(m, n); + } +} +pub fn map_setall(m: *mut Map) { + unsafe { + map_setall_static(m); + } +} +// pub fn map_clr(m: *mut Map, n: i32) { +// unsafe { +// map_clr_static(m, n); +// } +// } +// pub fn map_tst(m: *mut Map, n: i32) { +// unsafe { +// map_tst_static(m, n); +// } +// } +// pub fn map_clr_at(m: *mut Map, n: i32) { +// unsafe { +// map_clr_at_static(m, n); +// } +// } +pub fn set_callbackfunction(ts: rpmts, quiet: bool, term_width: u16) -> i32 { + unsafe { set_callback_fn(ts, quiet as i32, term_width) } +} diff --git a/rdnf/src/c_lib/queue_static.c b/rdnf/src/c_lib/queue_static.c new file mode 100644 index 00000000..bab91434 --- /dev/null +++ b/rdnf/src/c_lib/queue_static.c @@ -0,0 +1,159 @@ +#include +#include +#include +#include +char* char_ptr_offset_bind(char* p,int offset){ + + return p+offset; +} +void queue_empty_static(Queue *q) +{ + if (q->alloc) + { + q->left += (q->elements - q->alloc) + q->count; + q->elements = q->alloc; + } + else + q->left += q->count; + q->count = 0; +} + +void queue_push_static(Queue *q, Id id) +{ + if (!q->left) + queue_alloc_one(q); + q->elements[q->count++] = id; + q->left--; +} + +void queue_push2_static(Queue *q, Id id1, Id id2) +{ + queue_push(q, id1); + queue_push(q, id2); +} + +Dataiterator create_data_iterator_empty_bind() +{ + Dataiterator di = {0}; + return di; +} + +Repo create_Repo_empty_bind() +{ + Repo r = {0}; + return r; +} + +void dataiterator_init_simple_bind(Dataiterator *di, Pool *pool, const char *match, int flags) +{ + dataiterator_init(di, pool, 0, 0, 0, match, flags); +} + +void dataiterator_set_search_simple_bind(Dataiterator *di) +{ + dataiterator_set_search(di, 0, 0); +} + +int get_queue_element_value_bind(Queue *q, int index) +{ + if (index >= q->count) + { + return -1; + }; + return q->elements[index]; +} + +Solvable *pool_id2solvable_static(const Pool *pool, Id p) +{ + return pool->solvables + p; +} +void solv_add_flags_to_jobs_bind(Queue *q, int flags) +{ + for (int i = 0; i < q->count; i += 2) + { + q->elements[i] |= flags; + } +} +Solvable *get_pool_solvables_value_bind(const Pool *pool, unsigned int index) +{ + return &(pool->solvables[index]); +} +int is_pseudo_package_static(Pool *pool, Solvable *s) +{ + const char *n = pool_id2str(pool, s->name); + if (*n == 'p' && !strncmp(n, "patch:", 6)) + { + return 1; + } + return 0; +} +Repo *pool_id2repo_static(Pool *pool, Id repoid) +{ + return repoid < pool->nrepos ? pool->repos[repoid] : NULL; +} +Id pool_whatprovides_static(Pool *pool, Id d) +{ + if (!ISRELDEP(d)) + { + if (pool->whatprovides[d]) + return pool->whatprovides[d]; + } + else + { + Id v = GETRELID(d); + if (pool->whatprovides_rel[v]) + return pool->whatprovides_rel[v]; + } + return pool_addrelproviders(pool, d); +} +Id get_pool_whatprovidesdata_value_bind(Pool *pool, Id index) +{ + return pool->whatprovidesdata[index]; +} +int pool_match_nevr_static(Pool *pool, Solvable *s, Id d) +{ + if (!ISRELDEP(d)) + return d == s->name; + else + return pool_match_nevr_rel(pool, s, d); +} +int pool_disabled_solvable_static(const Pool *pool, Solvable *s) +{ + if (s->repo && s->repo->disabled) + return 1; + if (pool->considered) + { + Id id = s - pool->solvables; + if (!MAPTST(pool->considered, id)) + return 1; + } + return 0; +} + +void map_empty_static(Map *m) +{ + MAPZERO(m); +} +void map_set_static(Map *m, int n) +{ + MAPSET(m, n); +} +void map_setall_static(Map *m) +{ + MAPSETALL(m); +} +void map_clr_static(Map *m, int n) +{ + MAPCLR(m, n); +} +int map_tst_static(Map *m, int n) +{ + return MAPTST(m, n); +} +void map_clr_at_static(Map *m, int n) +{ + MAPCLR_AT(m, n); +} + + +// void rdnf_set diff --git a/rdnf/src/c_lib/rpm_trans.c b/rdnf/src/c_lib/rpm_trans.c new file mode 100644 index 00000000..04479387 --- /dev/null +++ b/rdnf/src/c_lib/rpm_trans.c @@ -0,0 +1,129 @@ +#include +#include +#include +#include +#include +typedef struct _CALL_BACK_CONTEXT_ +{ + int quiet; + FD_t fd; +} CallbackContext; + +void *rdnf_callback_fn( + const void *pArg, + const rpmCallbackType what, + const rpm_loff_t amount, + const rpm_loff_t total, + fnpyKey key, + rpmCallbackData data) +{ + Header pkg_header_ptr = (Header)pArg; + void *pResult = NULL; + char *file_path = (char *)key; + CallbackContext *context = (CallbackContext *)data; + int quiet=context->quiet %2; + int term_width=context->quiet >> 1; + char *nevra = headerGetAsString(pkg_header_ptr, RPMTAG_NEVRA); + int len=0; + term_width=term_width > 250?250:term_width; + term_width=term_width < 80 ? 80:term_width; + switch (what) + { + case RPMCALLBACK_INST_OPEN_FILE: + if ((!file_path) || !(*file_path)) + { + return NULL; + } + context->fd = Fopen(file_path, "r.udfio"); + return (void *)context->fd; + break; + case RPMCALLBACK_INST_CLOSE_FILE: + if (context->fd) + { + Fclose(context->fd); + context->fd = NULL; + } + break; + case RPMCALLBACK_INST_START: + if (!quiet) + { + len= term_width - strlen("Installing") - 2; + printf("%-*s\e[32mInstalling\e[0m \r", len, nevra); + (void)fflush(stdout); + } + break; + case RPMCALLBACK_INST_STOP: + if(!quiet){ + len=term_width-strlen("Installed")-2; + printf("%-*s\e[32mInstalled\e[0m \n", len, nevra); + (void)fflush(stdout); + } + break; + case RPMCALLBACK_UNINST_START: + if (!quiet) + { + len = term_width - strlen("Removing") - 2; + printf("%-*s\e[32mRemoving\e[0m \r", len, nevra); + (void)fflush(stdout); + } + break; + case RPMCALLBACK_UNINST_STOP: + if (!quiet){ + len=term_width-strlen("Removed")-2; + printf("%-*s\e[32mRemoved\e[0m \n",len,nevra); + (void)fflush(stdout); + } + break; + case RPMCALLBACK_SCRIPT_ERROR: + { + /* https://bugzilla.redhat.com/show_bug.cgi?id=216221#c15 */ + const char *pszScript; + switch (amount) + { + case RPMTAG_PREIN: + pszScript = "%prein"; + break; + case RPMTAG_POSTIN: + pszScript = "%postin"; + break; + case RPMTAG_PREUN: + pszScript = "%preun"; + break; + case RPMTAG_POSTUN: + pszScript = "%postun"; + break; + default: + pszScript = "(unknown)"; + break; + } + /* %pre and %preun will cause errors (install/uninstall will fail), + other scripts just warn (install/uninstall will succeed) */ + if (total == RPMRC_OK) + { + len = term_width - strlen("warning in ") - 12; + printf("%-*s\e[33mwarning in \e[0m%-*s", len, nevra, 12, pszScript); + (void)fflush(stdout); + } + else + { + len = term_width- strlen("error in ") - 12; + printf("%-*s\e[31error in \e[0m%-*s", len, nevra, 12, pszScript); + (void)fflush(stdout); + } + } + break; + default: + break; + } + if (nevra!=NULL){ + free(nevra); + } + return pResult; +} +int set_callback_fn(rpmts ts, int quiet, uint16_t term_width) +{ + CallbackContext p = {0}; + p.quiet = term_width <<1; + p.quiet +=quiet; + return rpmtsSetNotifyCallback(ts, rdnf_callback_fn, (void *)&p); +} \ No newline at end of file diff --git a/rdnf/src/cli.rs b/rdnf/src/cli.rs new file mode 100644 index 00000000..25e0c5d2 --- /dev/null +++ b/rdnf/src/cli.rs @@ -0,0 +1,168 @@ +use anyhow::bail; +use anyhow::Result; +use clap::arg; +use clap::command; +use clap::Args; +use clap::Parser; +use clap::Subcommand; + +use crate::default::RDNF_CONF_FILE; +use crate::errors::ERROR_RDNF_RPM_INIT; +use clap::ArgAction::Append; +use clap::ArgAction::SetTrue; +#[derive(Parser, Debug, Clone)] +#[command(name = "rdnf", version = "1.0")] +pub struct Cli { + #[command(subcommand)] + pub command: Commands, + #[arg(default_value_t=String::from("/"),long,value_parser)] + pub installroot: String, + #[arg(default_value_t=String::from(RDNF_CONF_FILE),short='c',long,value_parser)] + pub config_file: String, + #[arg(long,action=SetTrue)] + pub plugins: bool, + ///download all dependencies even if already installed + #[arg(long,action=SetTrue)] + pub alldeps: bool, + ///enable repoid,don't enable or disable other repoids + #[arg(long,value_name="repoid",action=Append)] + pub enablerepo: Option>, + ///disable repoid,don't disable or enable other repoids + #[arg(long,value_name="repoid",action=Append)] + pub disablerepo: Option>, + ///enable repoids and disable other repoids + #[arg(long,value_name="repoid",action=Append)] + pub repoid: Option>, + #[arg(long, value_parser)] + pub releasever: Option, + #[arg(long, value_parser)] + pub reposdir: Option, + #[arg(long,action=SetTrue)] + pub cacheonly: bool, + #[arg(long,action=SetTrue)] + pub refresh: bool, + #[arg(long,action=SetTrue)] + pub security: bool, + #[arg(long, value_name = "severity")] + pub sec_severity: Option, + ///set to rpm verbosity level (emergency,alert,critical,error,warning,notice,info,debug) + #[arg(default_value_t=String::from("error"),long,value_name="debug level name")] + pub rpm_verbosity: String, + #[arg(long,action=SetTrue)] + pub reboot_required: bool, + #[arg(long,action=SetTrue)] + pub disable_excludes: bool, + #[arg(long,value_name="pkg",action=Append)] + pub exclude: Option>, +} +#[derive(Subcommand, Debug, Clone)] +pub enum Commands { + Repolist(RepolistOption), + Makecache, + Search { pkgs: Vec }, + Remove(AlterOption), + Install(AlterOption), + Reinstall(AlterOption), + Update(AlterOption), + Info(InfoOption), +} +#[derive(Args, Clone, Debug, Copy)] +pub struct RepolistOption { + #[arg(long,action=clap::ArgAction::SetTrue)] + pub all: bool, + #[arg(long,action=clap::ArgAction::SetTrue)] + pub enabled: bool, + #[arg(long,action=clap::ArgAction::SetTrue)] + pub disabled: bool, +} +#[derive(Args, Clone, Debug)] +pub struct AlterOption { + pub pkgs: Vec, + ///allow erasures when solving + #[arg(long,action=SetTrue)] + pub allow_erasing: bool, + ///assume no for all questions + #[arg(long,action=SetTrue)] + pub assume_no: bool, + ///assume yes for all questions + #[arg(long,action=SetTrue)] + pub assume_yes: bool, + ///download packages only, no install + #[arg(long,action=SetTrue)] + pub download_only: bool, + #[arg(long,action=SetTrue)] + pub quiet: bool, + ///resolve packages to latest version + #[arg(long,action=SetTrue)] + pub best: bool, + ///dump solv debug info + #[arg(long,action=SetTrue)] + pub debug_solver: bool, + ///overide clean_requirements_on_remove config option + #[arg(long,action=SetTrue)] + pub no_auto_remove: bool, + ///skip gpg check + #[arg(long,action=SetTrue)] + pub no_gpg_check: bool, + ///skip conflict problems + #[arg(long,action=SetTrue)] + pub skip_confilicts: bool, + ///skip verifying RPM digest + #[arg(long,action=SetTrue)] + pub skip_digest: bool, + ///skip obsolete problems + #[arg(long,action=SetTrue)] + pub skip_obsolete: bool, + ///skip verifying RPM signatures + #[arg(long,action=SetTrue)] + pub skip_signatures: bool, + #[arg(long,action=SetTrue)] + pub tsflags_noscripts: bool, +} +#[derive(Args, Clone, Debug)] +pub struct InfoOption{ + pub pkgs:Vec, + #[arg(long,action=SetTrue)] + pub all: bool, + #[arg(long,action=SetTrue)] + pub installed: bool, + #[arg(long,action=SetTrue)] + pub available: bool, + #[arg(long,action=SetTrue)] + pub extras:bool, + #[arg(long,action=SetTrue)] + pub obsoletes:bool, + #[arg(long,action=SetTrue)] + pub recent:bool, + #[arg(long,action=SetTrue)] + pub upgrades:bool, + #[arg(long,action=SetTrue)] + pub updates:bool, + #[arg(long,action=SetTrue)] + pub dwongrades:bool, +} +impl Cli { + pub fn init(self) -> Result { + let cli = self.check_fs_path(); + Ok(cli) + } + pub fn check_fs_path(mut self) -> Self { + self.installroot = self.installroot.trim_end_matches("/").to_string() + "/"; + self.config_file = self.installroot.clone() + self.config_file.trim_start_matches("/"); + match self.reposdir { + Some(s) => { + self.reposdir = Some(self.installroot.clone() + s.trim_start_matches("/")); + } + None => {} + } + self + } +} +pub fn rpm_init() -> Result<()> { + unsafe { + if rpm_sys::ffi::rpmReadConfigFiles(0 as *mut i8, 0 as *mut i8) != 0 { + bail!(ERROR_RDNF_RPM_INIT); + }; + } + Ok(()) +} diff --git a/rdnf/src/conf.rs b/rdnf/src/conf.rs new file mode 100644 index 00000000..b57b1a71 --- /dev/null +++ b/rdnf/src/conf.rs @@ -0,0 +1,204 @@ +use std::{ + ffi::{CStr, CString}, + os::raw::c_void, +}; + +use anyhow::{bail, Result}; +use config::{Config, ConfigError, File, FileFormat}; +use libc::utsname; +use serde::Deserialize; + +use rpm_sys::ffi::{ + headerGetString, headerLink, rpmTag_e_RPMTAG_VERSION, rpmdbNextIterator, rpmtsInitIterator, +}; + +use crate::{ + cli::Cli, + default::{ + DEFAULT_CACHE_LOCATION, DEFAULT_DISTROVERPKG, DEFAULT_PLUGIN_CONF_PATH, + DEFAULT_PLUGIN_PATH, DEFAULT_REPO_LOCATION, + }, + errors::{ + ERROR_RDNF_DISTROVERPKG_READ, ERROR_RDNF_NO_DISTROVERPKG, ERROR_RDNF_RPMTS_CREATE_FAILED, + }, + utils::check_dir, +}; + +#[derive(Debug, Deserialize, Clone)] +struct Main { + installonly_limit: Option, + clean_requirements_on_remove: Option, + gpgcheck: Option, + keepcache: Option, + repodir: Option, + cachedir: Option, + proxy: Option, + proxy_username: Option, + proxy_password: Option, + distroverpkg: Option, + excludepkgs: Option>, + minversions: Option>, + plugins: Option, + pluginconfpath: Option, + pluginpath: Option, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct Configfile { + main: Main, +} +#[derive(Debug, Clone)] +pub struct ConfigMain { + pub installonly_limit: usize, + pub clean_requirements_on_remove: bool, + pub gpgcheck: bool, + pub keepcache: bool, + pub repodir: String, + pub cachedir: String, + pub distroverpkg: String, + pub excludepkgs: Option>, + pub minversions: Option>, + pub proxy: Option, + pub proxy_username: Option, + pub proxy_password: Option, + pub plugins: bool, + pub pluginconfpath: String, + pub pluginpath: String, + pub var_release_ver: String, + pub var_base_arch: String, +} +impl ConfigMain { + pub fn from(cli: &mut Cli) -> Result { + let main = match Config::builder() + .add_source(File::new(&cli.config_file, FileFormat::Ini)) + .build() + { + Ok(s) => { + let c: Result = s.try_deserialize(); + match c { + Ok(t) => { + let m = t.main; + let pkg = m.distroverpkg.unwrap_or(String::from(DEFAULT_DISTROVERPKG)); + ConfigMain { + gpgcheck: m.gpgcheck.unwrap_or(false), + installonly_limit: m.installonly_limit.unwrap_or(1), + clean_requirements_on_remove: m + .clean_requirements_on_remove + .unwrap_or(false), + keepcache: m.keepcache.unwrap_or(false), + repodir: { + match &cli.reposdir { + Some(s) => s.to_string(), + None => { + cli.installroot.to_string() + + m.repodir + .unwrap_or(String::from(DEFAULT_REPO_LOCATION)) + .trim_start_matches("/") + .trim_end_matches("/") + + "/" + } + } + }, + cachedir: cli.installroot.to_string() + + m.cachedir + .unwrap_or(String::from(DEFAULT_CACHE_LOCATION)) + .trim_start_matches("/") + .trim_end_matches("/") + + "/", + distroverpkg: pkg.clone(), + excludepkgs: m.excludepkgs, + minversions: m.minversions, + proxy: m.proxy, + proxy_username: m.proxy_username, + proxy_password: m.proxy_password, + plugins: if cli.plugins { + true + } else { + if m.plugins.unwrap_or(false) { + cli.plugins = true; + true + } else { + false + } + }, + pluginconfpath: m + .pluginconfpath + .unwrap_or(String::from(DEFAULT_PLUGIN_CONF_PATH)), + pluginpath: m.pluginpath.unwrap_or(String::from(DEFAULT_PLUGIN_PATH)), + var_release_ver: { ConfigMain::get_package_version(pkg, cli)? }, + var_base_arch: { ConfigMain::get_kernel_arch()? }, + } + } + Err(_) => { + bail!("Failed to parse config file: {}", cli.config_file) + } + } + } + Err(_) => { + bail!("Failed to read config file: {}", cli.config_file) + } + }; + if !check_dir(main.repodir.as_str())? { + bail!("Dir repodir {} don't have .repo file", main.repodir); + }; + Ok(main) + } + pub fn get_package_version(pkg: String, cli: &mut Cli) -> Result { + let ver = match cli.releasever.clone() { + Some(ver) => ver, + None => unsafe { + let p_ts = rpm_sys::ffi::rpmtsCreate(); + if p_ts.is_null() { + bail!(ERROR_RDNF_RPMTS_CREATE_FAILED); + } + let root_ptr = CString::new(cli.installroot.clone()) + .unwrap_or(CString::new("/").unwrap()); + if rpm_sys::ffi::rpmtsSetRootDir(p_ts, root_ptr.as_ptr()) != 0 { + bail!("Failed to set root dir {} for rpmts", cli.installroot); + }; + let pkg_t = CString::new(pkg.as_str()).unwrap(); + let t = pkg_t.as_ptr() as *const c_void; + let p_iter = rpmtsInitIterator(p_ts, 1047, t, 0); + if p_iter.is_null() { + bail!(ERROR_RDNF_NO_DISTROVERPKG) + }; + let p_header = rpmdbNextIterator(p_iter); + if p_header.is_null() { + bail!(ERROR_RDNF_DISTROVERPKG_READ) + }; + let p_header = headerLink(p_header); + if p_header.is_null() { + bail!(ERROR_RDNF_DISTROVERPKG_READ) + }; + let psz_version_temp = headerGetString(p_header, rpmTag_e_RPMTAG_VERSION); + if psz_version_temp.is_null() { + bail!(ERROR_RDNF_DISTROVERPKG_READ) + }; + let version = CStr::from_ptr(psz_version_temp).to_str().unwrap(); + String::from(version) + }, + }; + Ok(ver) + } + pub fn get_kernel_arch() -> Result { + let c = [0; 65]; + let mut system_info = utsname { + sysname: c, + nodename: c, + release: c, + version: c, + machine: c, + domainname: c, + }; + unsafe { + if libc::uname(&mut system_info) != 0 { + bail!("Failed to uname"); + }; + let arch = CStr::from_ptr(system_info.machine.as_ptr()) + .to_str() + .unwrap() + .to_string(); + Ok(arch) + } + } +} diff --git a/rdnf/src/default.rs b/rdnf/src/default.rs new file mode 100644 index 00000000..b75d8e2c --- /dev/null +++ b/rdnf/src/default.rs @@ -0,0 +1,38 @@ +pub const RDNF_NAME: &str = "rdnf"; +// pub const RDNF_CONF_FILE: &str = "/etc/rdnf/rdnf.conf"; +pub const RDNF_CONF_FILE:&str="/etc/dnf/dnf.conf"; +// pub const SYSTEM_LIBDIR: &str = "/usr/local/lib64"; +pub const DEFAULT_REPO_LOCATION: &str = "/etc/yum.repos.d"; +pub const DEFAULT_CACHE_LOCATION: &str = "/var/cache/rdnf"; +// pub const DEFAULT_DATA_LOCATION: &str = "/var/lib/rdnf"; +// pub const HISTORY_DB_FILE:&str="history.db"; +pub const DEFAULT_DISTROVERPKG: &str = "system-release"; +pub const DEFAULT_PLUGIN_CONF_PATH: &str = "/etc/tdnf/pluginconf.d"; +pub const DEFAULT_PLUGIN_PATH: &str = "/usr/local/lib64/tdnf-plugins"; +pub const VAR_RELEASEVER: &str = "$releasever"; +pub const VAR_BASEARCH: &str = "$basearch"; +pub const SYSTEM_REPO_NAME: &str = "@System"; +pub const CMDLINE_REPO_NAME: &str = "@cmdline"; +pub const REPO_METADATA_MARKER: &str = "lastrefresh"; +pub const REPODATA_DIR_NAME: &str = "repodata"; +pub const SOLVCACHE_DIR_NAME: &str = "solvcache"; +pub const RPM_CACHE_DIR_NAME: &str = "rpms"; +pub const GPGKEY_CACHE_DIR_NAME:&str="keys"; +pub const REPO_METADATA_FILE_NAME: &str = "repomd.xml"; +pub const REPO_METADATA_FILE_PATH: &str = "repodata/repomd.xml"; +pub const REPO_METALINK_FILE_NAME: &str = "metalink"; +pub const REPO_BASEURL_FILE_NAME: &str = "baseurl"; +// pub const PROGRESS_BAR_STYLE:&str="{{msg:{}}}{{spinner:.green}}[{{bar:{}.cyan/blue}}] {{bytes}}/{{total_bytes}} ({{bytes_per_sec}},{{eta}})"; +pub const SOLV_COOKIE_IDENT: &str = "tdnf"; +pub const SOLV_COOKIE_LEN: usize = 32; +pub const RDNF_INSTANCE_LOCK_FILE: &str = "/var/run/.rdnf-instance-lockfile"; + +// use console::Term; +// use lazy_static::lazy_static; +// lazy_static! { +// pub static ref TERM_WIDTH:u16= { +// let term = Term::stdout(); +// let (_,width)=term.size(); +// width +// }; +// } diff --git a/rdnf/src/errors.rs b/rdnf/src/errors.rs new file mode 100644 index 00000000..f5d54206 --- /dev/null +++ b/rdnf/src/errors.rs @@ -0,0 +1,166 @@ +pub const ERROR_RDNF_INVALID_PARAMETER: &str = "unknown system error,Invalid argument"; +pub const ERROR_RDNF_OUT_OF_MEMORY: &str = "unknown system error , Out of memory "; +pub const ERROR_RDNF_NO_DATA: &str = "unknown system error , No data available"; +pub const ERROR_RDNF_NO_MATCH: &str = "No matching packages"; +pub const ERROR_RDNF_ALREADY_EXISTS: &str = "unknown system error , File exists"; +pub const ERROR_RDNF_NO_DISTROVERPKG:&str= "distroverpkg config entry is set to a package that is not installed. Check /etc/tdnf/tdnf.conf"; +pub const ERROR_RDNF_RPM_INIT: &str = "Error initializing rpm config.Check /usr/lib/rpm/rpmrc"; +pub const ERROR_RDNF_DISTROVERPKG_READ: &str = "There was an error reading version of distroverpkg"; +pub const ERROR_RDNF_SOLV_FAILED: &str = "Solv general runtime error"; +pub const ERROR_RDNF_SOLV_IO: &str = "Solv - I/O error"; +pub const ERROR_RDNF_SOLV_CHKSUM: &str = "Solv - Checksum creation failed"; +pub const ERROR_RDNF_REPO_WRITE: &str = "Solv - Failed to write repo"; +pub const ERROR_RDNF_ADD_SOLV: &str = "Solv - Failed to add solv"; +pub const ERROR_RDNF_RPMTS_CREATE_FAILED: &str = "RPM transaction set could not be created"; +pub const ERROR_RDNF_OPERATION_ABORTED: &str = "Operation aborted."; +pub const ERROR_RDNF_REPO_NOT_FOUND: &str = "Repo was not found"; + +pub const ERROR_RDNF_CACHE_REFRESH: &str = r#"rdnf repo cache needs to be refreshed +You cam use one of the below methods to workaround this +1. Login as root and refresh cache +2. Use --config option and create repo cache where you have access +3. Use --cacheonly and use existing cache in the system"#; +pub const ERROR_RDNF_NO_GPGKEY_CONF_ENTRY:&str="gpgkey entry is missing for this repo. please add gpgkey in repo file or use --nogpgcheck to ignore."; +pub const ERROR_RDNF_URL_INVALID: &str = "URL is invalid."; +pub const ERROR_RDNF_RPM_CHECK: &str = "rpm check reported errors"; +pub const ERROR_RDNF_INVALID_RESOLVE_ARG: &str = "Invalid argument in resolve"; +pub const ERROR_RDNF_SELF_ERASE: &str = + "The operation would result in removing the protected package : tdnf"; +pub const ERROR_RDNF_NOTHING_TO_DO: &str = "Nothing to do."; +pub const ERROR_RDNF_TRANSACTION_FAILED: &str = "rpm transaction failed"; +pub const ERROR_RDNF_INVALID_PUBKEY_FILE: &str = "public key file is invalid or corrupted"; +pub const ERROR_RDNF_RPMTD_CREATE_FAILED: &str = + "RPM data container could not be created. Use --nogpgcheck to ignore."; + +pub const ERROR_RDNF_RPM_GET_RSAHEADER_FAILED: &str = + "RPM not signed. Use --skipsignature or --nogpgcheck to ignore."; +pub const ERROR_RDNF_RPM_GPG_PARSE_FAILED: &str = + "RPM failed to parse gpg key. Use --nogpgcheck to ignore."; +pub const ERROR_RDNF_RPM_GPG_NO_MATCH: &str = + "RPM is signed but failed to match with known keys. Use --nogpgcheck to ignore."; + +// pub const ERROR_RDNF_INVALID_ADDRESS: &str = "unknown system error , Bad address "; +// pub const ERROR_RDNF_CALL_INTERRUPTED: &str = "unknown system error , Interrupted system call"; +// pub const ERROR_RDNF_FILESYS_IO: &str = "unknown system error , I/O error"; +// pub const ERROR_RDNF_SYM_LOOP: &str = "unknown system error , Too many symbolic links encountered "; +// pub const ERROR_RDNF_NAME_TOO_LONG: &str = "unknown system error , File name too long"; +// pub const ERROR_RDNF_CALL_NOT_SUPPORTED: &str = +// "unknown system error , Invalid system call number "; +// pub const ERROR_RDNF_INVALID_DIR: &str = "unknown system error , Not a directory"; +// pub const ERROR_RDNF_OVERFLOW: &str = +// "unknown system error , Value too large for defined data type"; +// pub const ERROR_RDNF_PACKAGE_REQUIRED: &str = "Package name expected but was not provided"; +// pub const ERROR_RDNF_CONF_FILE_LOAD: &str = "Error loading tdnf conf (/etc/tdnf/tdnf.conf)"; +// pub const ERROR_RDNF_REPO_FILE_LOAD: &str = +// "Error loading tdnf repo (normally under /etc/yum.repos.d"; +// pub const ERROR_RDNF_INVALID_REPO_FILE: &str = "Encountered an invalid repo file"; +// pub const ERROR_RDNF_REPO_DIR_OPEN:&str= "Error opening repo dir. Check if the repodir configured in tdnf.conf exists (usually /etc/yum.repos.d)"; +// pub const ERROR_RDNF_SET_PROXY: &str = "There was an error setting the proxy server."; +// pub const ERROR_RDNF_SET_PROXY_USERPASS: &str = +// "There was an error setting the proxy server user and pass"; +// pub const ERROR_RDNF_INVALID_ALLOCSIZE: &str = +// "A memory allocation was requested with an invalid size"; +// pub const ERROR_RDNF_STRING_TOO_LONG: &str = "Requested string allocation size was too long."; +// pub const ERROR_RDNF_NO_ENABLED_REPOS:&str= "There are no enabled repos.\n Run \"tdnf repolist all\" to see the repos you have.\n You can enable repos by\n 1. +// by passing in --enablerepo \n 2. editing repo files in your repodir(usually /etc/yum.repos.d)"; +// pub const ERROR_RDNF_PACKAGELIST_EMPTY: &str = "Packagelist was empty"; +// pub const ERROR_RDNF_GOAL_CREATE: &str = "Error creating goal"; + +// pub const ERROR_RDNF_CLEAN_UNSUPPORTED: &str = +// "Clean type specified is not supported in this release. Please try clean all."; +// pub const ERROR_RDNF_SOLV_BASE: &str = "Solv base error"; + +// pub const ERROR_RDNF_SOLV_OP: &str = "Solv client programming error"; +// pub const ERROR_RDNF_SOLV_LIBSOLV: &str = "Solv error propagted from libsolv"; + +// pub const ERROR_RDNF_SOLV_CACHE_WRITE: &str = "Solv - cache write error"; +// pub const ERROR_RDNF_SOLV_QUERY: &str = "Solv - ill formed query"; +// pub const ERROR_RDNF_SOLV_ARCH: &str = "Solv - unknown arch"; +// pub const ERROR_RDNF_SOLV_VALIDATION: &str = "Solv - validation check failed"; +// pub const ERROR_RDNF_SOLV_NO_SOLUTION: &str = "Solv - goal found no solutions"; +// pub const ERROR_RDNF_SOLV_NO_CAPABILITY: &str = "Solv - the capability was not available"; + +// pub const ERROR_RDNF_SOLV_CACHE_NOT_CREATED: &str = "Solv - Solv cache not found"; + +// pub const ERROR_RDNF_REPO_BASE: &str = "Repo error base"; +// pub const ERROR_RDNF_SET_SSL_SETTINGS: &str = +// "There was an error while setting SSL settings for the repo."; +// pub const ERROR_RDNF_REPO_PERFORM: &str = "Error during repo handle execution"; +// pub const ERROR_RDNF_REPO_GETINFO: &str = "Repo during repo result getinfo"; + +// pub const ERROR_RDNF_NO_SEARCH_RESULTS: &str = "No matches found"; +// pub const ERROR_RDNF_RPMRC_NOTFOUND: &str = +// "rpm generic error - not found (possible corrupt rpm file)"; +// pub const ERROR_RDNF_RPMRC_FAIL: &str = "rpm generic failure"; +// pub const ERROR_RDNF_RPMRC_NOTTRUSTED: &str = "rpm signature is OK, but key is not trusted"; +// pub const ERROR_RDNF_RPMRC_NOKEY:&str="public key is unavailable. install public key using rpm --import or use --nogpgcheck to ignore."; + +// pub const ERROR_RDNF_KEYURL_UNSUPPORTED: &str = +// "GpgKey Url schemes other than file are not supported"; +// pub const ERROR_RDNF_KEYURL_INVALID: &str = "GpgKey Url is invalid"; +// pub const ERROR_RDNF_RPM_NOT_SIGNED: &str = "RPM not signed. Use --nogpgcheck to ignore."; + +// pub const ERROR_RDNF_AUTOERASE_UNSUPPORTED: &str = "autoerase / autoremove is not supported."; + +// pub const ERROR_RDNF_METADATA_EXPIRE_PARSE: &str = +// "metadata_expire value could not be parsed. Check your repo files."; + +// pub const ERROR_RDNF_DOWNGRADE_NOT_ALLOWED:&str="a downgrade is not allowed below the minimal version. Check 'minversions' in the configuration."; +// pub const ERROR_RDNF_PERM: &str = "Operation not permitted. You have to be root."; +// pub const ERROR_RDNF_OPT_NOT_FOUND: &str = "A required option was not found"; + +// pub const ERROR_RDNF_INVALID_INPUT: &str = "Invalid input."; +// pub const ERROR_RDNF_CACHE_DISABLED: &str = "cache only is set, but no repo data found"; +// pub const ERROR_RDNF_CACHE_DIR_OUT_OF_DISK_SPACE:&str="Insufficient disk space at cache directory /var/cache/tdnf (unless specified differently in config). Try freeing space first."; +// pub const ERROR_RDNF_EVENT_CTXT_ITEM_NOT_FOUND:&str="An event context item was not found. This is usually related to plugin events. Try --noplugins to deactivate all plugins or --disableplugin= to deactivate a specific one. You can permanently deactivate an offending plugin by setting enable=0 in the plugin config file."; +// pub const ERROR_RDNF_EVENT_CTXT_ITEM_INVALID_TYPE:&str="An event item type had a mismatch. This is usually related to plugin events. Try --noplugins to deactivate all plugins or --disableplugin= to deactivate a specific one. You can permanently deactivate an offending plugin by setting enable=0 in the plugin config file."; + +// pub const ERROR_RDNF_BASEURL_DOES_NOT_EXISTS: &str = +// "Base URL and Metalink URL not found in the repo file"; +// pub const ERROR_RDNF_CHECKSUM_VALIDATION_FAILED: &str = +// "Checksum Validation failed for the repomd.xml downloaded using URL from metalink"; +// pub const ERROR_RDNF_METALINK_RESOURCE_VALIDATION_FAILED: &str = +// "No Resource present in metalink file for file download"; +// pub const ERROR_RDNF_FIPS_MODE_FORBIDDEN: &str = "API call to digest API forbidden in FIPS mode!"; +// pub const ERROR_RDNF_CURLE_UNSUPPORTED_PROTOCOL: &str = "Curl doesn't Support this protocol"; +// pub const ERROR_RDNF_CURLE_FAILED_INIT: &str = "Curl Init Failed"; +// pub const ERROR_RDNF_CURLE_URL_MALFORMAT: &str = +// "URL seems to be corrupted. Please clean all and makecache"; +// pub const ERROR_RDNF_SYSTEM_BASE: &str = "unknown system error"; +// pub const ERROR_RDNF_ML_PARSER_INVALID_DOC_OBJECT: &str = +// "Failed to parse and create document tree"; +// pub const ERROR_RDNF_ML_PARSER_INVALID_ROOT_ELEMENT: &str = "Root element not found"; +// pub const ERROR_RDNF_ML_PARSER_MISSING_FILE_ATTR: &str = "Missing filename in metalink file"; +// pub const ERROR_RDNF_ML_PARSER_INVALID_FILE_NAME: &str = "Invalid filename present"; +// pub const ERROR_RDNF_ML_PARSER_MISSING_FILE_SIZE: &str = "Missing file size in metalink file"; +// pub const ERROR_RDNF_ML_PARSER_MISSING_HASH_ATTR: &str = "Missing attribute in hash tag"; +// pub const ERROR_RDNF_ML_PARSER_MISSING_HASH_CONTENT: &str = "Missing content in hash tag value"; +// pub const ERROR_RDNF_ML_PARSER_MISSING_URL_ATTR: &str = "Missing attribute in url tag"; +// pub const ERROR_RDNF_ML_PARSER_MISSING_URL_CONTENT: &str = "Missing content in url tag value"; +// pub const ERROR_RDNF_HISTORY_ERROR: &str = "History database error"; +// pub const ERROR_RDNF_HISTORY_NODB: &str = "History database does not exist"; + + + +// pub const ERROR_RDNF_BASE: &str = "Generic base error."; +// pub const ERROR_RDNF_INVALID_ARGUMENT: &str = "Invalid argument."; +// pub const ERROR_RDNF_CLEAN_REQUIRES_OPTION: &str = +// "Clean requires an option: packages, metadata, dbcache, plugins, expire-cache, all"; +// pub const ERROR_RDNF_NOT_ENOUGH_ARGS: &str = +// "The command line parser could not continue. Expected at least one argument."; + +// pub const ERROR_RDNF_OPTION_NAME_INVALID: &str = "Command line error: option is invalid."; +// pub const ERROR_RDNF_OPTION_ARG_REQUIRED: &str = "Command line error: expected one argument."; +// pub const ERROR_RDNF_OPTION_ARG_UNEXPECTED: &str = "Command line error: argument was unexpected."; +// pub const ERROR_RDNF_CHECKLOCAL_EXPECT_DIR: &str = +// "check-local requires path to rpm directory as a parameter"; +// pub const ERROR_RDNF_PROVIDES_EXPECT_ARG: &str = "Need an item to match."; +// pub const ERROR_RDNF_SETOPT_NO_EQUALS: &str = +// "Missing equal sign in setopt argument. setopt requires an argument of the form key=value."; +// pub const ERROR_RDNF_NO_SUCH_CMD: &str = "Please check your command"; +// pub const ERROR_RDNF_DOWNLOADDIR_REQUIRES_DOWNLOADONLY: &str = +// "--downloaddir requires --downloadonly"; +// pub const ERROR_RDNF_ONE_DEP_ONLY: &str = "only one dependency allowed"; +// pub const ERROR_RDNF_ALLDEPS_REQUIRES_DOWNLOADONLY: &str = "--alldeps requires --downloadonly"; +// pub const ERROR_RDNF_FILE_NOT_FOUND: &str = "unknown system error , No such file or directory"; +// pub const ERROR_RDNF_ACCESS_DENIED: &str = "unknown system error , Permission denied"; \ No newline at end of file diff --git a/rdnf/src/goal.rs b/rdnf/src/goal.rs new file mode 100644 index 00000000..1ed897cb --- /dev/null +++ b/rdnf/src/goal.rs @@ -0,0 +1,413 @@ +use std::{ffi::CString, mem::size_of}; + +use crate::{ + c_lib::{ + create_dataiterator_empty, get_queue_element_value, map_set, map_setall, pool_id2solvable, + queue_push, queue_push2, solv_add_flags_to_jobs, + }, + cli::AlterOption, + errors::{ + ERROR_RDNF_ALREADY_EXISTS, ERROR_RDNF_INVALID_PARAMETER, ERROR_RDNF_INVALID_RESOLVE_ARG, + ERROR_RDNF_OUT_OF_MEMORY, + }, + solv::{ + rdnf_pkg::{init_map, solv_add_excludes, SkipProblem}, + rdnf_query::{ + init_queue, + }, + SolvPackageList, + }, + sub_command::{install::{is_glob, AlterType}, info::{PkgInfo, PkgInfoLevel}}, + Rdnf, +}; +use anyhow::{bail, Result}; +use glob::Pattern; + +use solv_sys::ffi::{ + dataiterator_free, dataiterator_init, dataiterator_step, map_grow, map_init, map_subtract, + pool_evrcmp_str, s_Map, s_Solver, solv_knownid_SOLVABLE_EVR, solv_knownid_SOLVABLE_NAME, + solvable_lookup_str, solver_create, solver_create_transaction, solver_get_unneeded, + solver_set_flag, solver_solve, testcase_write, transaction_type, Queue, Repo, Transaction, + EVRCMP_COMPARE, SEARCH_STRING, SOLVER_ERASE, SOLVER_FLAG_ALLOW_DOWNGRADE, + SOLVER_FLAG_ALLOW_UNINSTALL, SOLVER_FLAG_ALLOW_VENDORCHANGE, SOLVER_FLAG_BEST_OBEY_POLICY, + SOLVER_FLAG_INSTALL_ALSO_UPDATES, SOLVER_FLAG_KEEP_ORPHANS, SOLVER_FLAG_YUM_OBSOLETES, + SOLVER_FORCEBEST, SOLVER_INSTALL, SOLVER_SOLVABLE, SOLVER_SOLVABLE_ALL, + SOLVER_TRANSACTION_DOWNGRADE, SOLVER_TRANSACTION_ERASE, SOLVER_TRANSACTION_INSTALL, + SOLVER_TRANSACTION_OBSOLETED, SOLVER_TRANSACTION_REINSTALL, SOLVER_TRANSACTION_SHOW_ACTIVE, + SOLVER_TRANSACTION_SHOW_ALL, SOLVER_TRANSACTION_SHOW_OBSOLETES, SOLVER_TRANSACTION_UPGRADE, + SOLVER_UPDATE, TESTCASE_RESULT_PROBLEMS, TESTCASE_RESULT_TRANSACTION, +}; +impl Rdnf { + pub fn get_pkgs_with_specified_type( + &self, + trans: *mut Transaction, + dw_type: i32, + ) -> Result>> { + let mut solved_pkgs = init_queue(); + unsafe { + for i in 0..(*trans).steps.count { + let pkg = get_queue_element_value(&mut (*trans).steps, i as u32); + let pkg_type = if dw_type == SOLVER_TRANSACTION_OBSOLETED as i32 { + transaction_type(trans, pkg, SOLVER_TRANSACTION_SHOW_OBSOLETES as i32) + } else { + transaction_type( + trans, + pkg, + (SOLVER_TRANSACTION_SHOW_ACTIVE | SOLVER_TRANSACTION_SHOW_ALL) as i32, + ) + }; + if dw_type == pkg_type { + queue_push(&mut solved_pkgs, pkg); + } + } + } + let pkg_list = match SolvPackageList::queue_to_pkg_list(&mut solved_pkgs) { + Ok(s) => Some(s), + Err(_) => None, + }; + if pkg_list.is_some() { + if pkg_list.as_ref().unwrap().get_size() > 0 { + let pkg_infos = PkgInfo::populate_pkg_info(&self.rc.sack, &pkg_list.unwrap(),PkgInfoLevel::Details)?; + if pkg_infos.is_empty() { + Ok(None) + } else { + Ok(Some(pkg_infos)) + } + } else { + Ok(None) + } + } else { + Ok(None) + } + } + pub fn goal( + &self, + pkg_list: &mut Queue, + alter_type: AlterType, + aler_args: &AlterOption, + ) -> Result { + let excludes = self.pkgs_to_exclude()?; + let mut queue_job = init_queue(); + if alter_type.is_upgrade_all() { + queue_push2( + &mut queue_job, + (SOLVER_UPDATE | SOLVER_SOLVABLE_ALL) as i32, + 0, + ); + } else if alter_type.is_distro_sync() { + } else { + if pkg_list.count == 0 { + bail!(ERROR_RDNF_ALREADY_EXISTS); + } + for i in 0..pkg_list.count { + let id = get_queue_element_value(pkg_list as *const Queue, i as u32); + self.add_goal(alter_type.clone(), &mut queue_job, id, &excludes)?; + } + } + let mut flags = 0; + if aler_args.best { + flags = flags | SOLVER_FORCEBEST; + } + solv_add_flags_to_jobs(&mut queue_job as *mut Queue, flags as i32); + + if !excludes.is_empty() { + solv_add_excludes(self.rc.sack.pool, &excludes); + } + self.solv_add_min_version(); + let solv = unsafe { solver_create(self.rc.sack.pool) }; + if solv.is_null() { + bail!(ERROR_RDNF_OUT_OF_MEMORY); + } + if aler_args.allow_erasing || alter_type.is_erase() || alter_type.is_auto_erase() { + unsafe { + solver_set_flag(solv, SOLVER_FLAG_ALLOW_UNINSTALL as i32, 1); + }; + } + let n_problems = unsafe { + solver_set_flag(solv, SOLVER_FLAG_BEST_OBEY_POLICY as i32, 1); + solver_set_flag(solv, SOLVER_FLAG_ALLOW_VENDORCHANGE as i32, 1); + solver_set_flag(solv, SOLVER_FLAG_KEEP_ORPHANS as i32, 1); + solver_set_flag(solv, SOLVER_FLAG_BEST_OBEY_POLICY as i32, 1); + solver_set_flag(solv, SOLVER_FLAG_YUM_OBSOLETES as i32, 1); + solver_set_flag(solv, SOLVER_FLAG_ALLOW_DOWNGRADE as i32, 1); + solver_set_flag(solv, SOLVER_FLAG_INSTALL_ALSO_UPDATES as i32, 1); + solver_solve(solv, &mut queue_job) + }; + if n_problems > 0 { + let mut skip_problem = self.get_skip_problem_opt(aler_args); + if alter_type.is_upgrade() && !excludes.is_empty() { + skip_problem.disabled = true; + } + self.rc.sack.solv_report_problems(solv, skip_problem)?; + } + let p_trans = unsafe { solver_create_transaction(solv) }; + if p_trans.is_null() { + bail!(ERROR_RDNF_INVALID_PARAMETER); + } + if aler_args.debug_solver { + let result_flags = TESTCASE_RESULT_TRANSACTION | TESTCASE_RESULT_PROBLEMS; + unsafe { + let dir = CString::new("debugdata").unwrap(); + if testcase_write( + solv, + dir.as_ptr(), + result_flags as i32, + 0 as *mut i8, + 0 as *mut i8, + ) == 0 + { + println!("Could not write debugdata to folder {}", "debugdata") + } + }; + } + Ok(self.goal_get_all_results_ignore_no_data(alter_type, p_trans, solv)?) + } + pub fn add_goal( + &self, + alter_type: AlterType, + queue_job: &mut Queue, + id: i32, + excludes: &Vec, + ) -> Result<()> { + if !excludes.is_empty() { + let pkg_name = self.rc.sack.solv_get_pkg_name_by_id(id)?; + for ele in excludes { + if is_glob(ele.as_str()) { + let p = Pattern::new(ele.as_str())?; + if p.matches(pkg_name.as_str()) { + return Ok(()); + }; + } else { + if ele == pkg_name.as_str() { + return Ok(()); + } + } + } + } + match alter_type { + AlterType::DownGrade | AlterType::DownGradeAll => { + queue_push2(queue_job, (SOLVER_SOLVABLE | SOLVER_INSTALL) as i32, id); + } + AlterType::AutoErase | AlterType::Erase => { + queue_push2(queue_job, (SOLVER_SOLVABLE | SOLVER_ERASE) as i32, id); + } + AlterType::ReInstall | AlterType::Install | AlterType::Upgrade => { + queue_push2(queue_job, (SOLVER_SOLVABLE | SOLVER_INSTALL) as i32, id); + } + // AlterType::AutoErase => { + // queue_push2( + // queue_job, + // (SOLVER_SOLVABLE | SOLVER_USERINSTALLED) as i32, + // id, + // ); + // } + _ => { + bail!(ERROR_RDNF_INVALID_RESOLVE_ARG) + } + } + Ok(()) + } + pub fn solv_add_min_version(&self) { + if self.rc.conf.minversions.is_some() { + let pool = self.rc.sack.pool; + let mut map_versins = unsafe { init_map((*pool).nsolvables) }; + for ele in self.rc.conf.minversions.as_ref().unwrap() { + let (name, ver) = ele.split_once("=").unwrap(); + let mut di = create_dataiterator_empty(); + unsafe { + let m = CString::new(name).unwrap(); + dataiterator_init( + &mut di, + pool, + 0 as *mut Repo, + 0, + solv_knownid_SOLVABLE_NAME as i32, + m.as_ptr(), + SEARCH_STRING as i32, + ); + while dataiterator_step(&mut di) != 0 { + let solv = pool_id2solvable(pool, di.solvid); + let evr = solvable_lookup_str(solv, solv_knownid_SOLVABLE_EVR as i32); + let evr2 = CString::new(ver).unwrap(); + if pool_evrcmp_str(pool, evr, evr2.as_ptr(), EVRCMP_COMPARE as i32) < 0 { + map_set(&mut map_versins, di.solvid); + } + } + dataiterator_free(&mut di); + } + } + unsafe { + if (*pool).considered.is_null() { + (*pool).considered = libc::malloc(size_of::()) as *mut s_Map; + map_init((*pool).considered, (*pool).nsolvables); + } else { + map_grow((*pool).considered, (*pool).nsolvables); + } + map_setall((*pool).considered); + map_subtract((*pool).considered, &mut map_versins); + } + } + } + pub fn get_skip_problem_opt(&self, aler_args: &AlterOption) -> SkipProblem { + let mut skip_problem = SkipProblem { + none: false, + conflicts: false, + obsoletes: false, + disabled: false, + }; + // match self.rc.cli.command { + // crate::cli::Commands::Check => skip_problem.none = true, + // _ => { + if aler_args.skip_confilicts { + skip_problem.conflicts = true; + } + if aler_args.skip_obsolete { + skip_problem.obsoletes = true; + } + // } + // }; + skip_problem + } + pub fn goal_get_all_results_ignore_no_data( + &self, + alter_type: AlterType, + trans: *mut Transaction, + solv: *mut s_Solver, + ) -> Result { + let mut solved_pkg_info = SolvedPkgInfoBase::default(); + solved_pkg_info.to_install = + self.get_pkgs_with_specified_type(trans, SOLVER_TRANSACTION_INSTALL as i32)?; + solved_pkg_info.to_upgrade = + self.get_pkgs_with_specified_type(trans, SOLVER_TRANSACTION_UPGRADE as i32)?; + solved_pkg_info.to_downgrade = + self.get_pkgs_with_specified_type(trans, SOLVER_TRANSACTION_DOWNGRADE as i32)?; + solved_pkg_info.removed_by_downgrade = if solved_pkg_info.to_downgrade.is_some() { + let mut pkg_to_remove = init_queue(); + for pkg_info in solved_pkg_info.to_downgrade.as_ref().unwrap() { + let pkg_id = self + .rc + .sack + .solv_find_installed_pkg_by_name(pkg_info.base.name.as_str())? + .get_pkg_id(0); + queue_push(&mut pkg_to_remove, pkg_id); + } + if pkg_to_remove.count > 0 { + let remove_pkg_list = SolvPackageList::queue_to_pkg_list(&mut pkg_to_remove)?; + Some(PkgInfo::populate_pkg_info(&self.rc.sack, &remove_pkg_list,PkgInfoLevel::Details)?) + } else { + None + } + } else { + None + }; + solved_pkg_info.to_remove = + self.get_pkgs_with_specified_type(trans, SOLVER_TRANSACTION_ERASE as i32)?; + solved_pkg_info.un_needed = if alter_type.is_auto_erase() { + let mut queue_result = init_queue(); + unsafe { solver_get_unneeded(solv, &mut queue_result, 0) }; + if queue_result.count > 0 { + let pkg_list = SolvPackageList::queue_to_pkg_list(&mut queue_result)?; + Some(PkgInfo::populate_pkg_info(&self.rc.sack, &pkg_list,PkgInfoLevel::Details)?) + } else { + None + } + } else { + None + }; + solved_pkg_info.to_reinstall = + self.get_pkgs_with_specified_type(trans, SOLVER_TRANSACTION_REINSTALL as i32)?; + solved_pkg_info.obsoleted = + self.get_pkgs_with_specified_type(trans, SOLVER_TRANSACTION_OBSOLETED as i32)?; + Ok(solved_pkg_info) + } +} + +#[derive(Debug)] +pub struct SolvedPkgInfo { + pub need_action: u32, + pub need_download: u32, + pub not_available: Option>, + pub existing: Option>, + pub not_resolved: Vec, + pub not_installed: Option>, + pub base: SolvedPkgInfoBase, +} +#[derive(Debug)] +pub struct SolvedPkgInfoBase { + pub to_install: Option>, + pub to_downgrade: Option>, + pub to_upgrade: Option>, + pub to_remove: Option>, + pub un_needed: Option>, + pub to_reinstall: Option>, + pub obsoleted: Option>, + pub removed_by_downgrade: Option>, +} +impl SolvedPkgInfoBase { + pub fn default() -> Self { + SolvedPkgInfoBase { + to_install: None, + to_downgrade: None, + to_upgrade: None, + to_remove: None, + un_needed: None, + to_reinstall: None, + obsoleted: None, + removed_by_downgrade: None, + } + } + pub fn get_need_action(&self) -> u32 { + let mut action = 0; + if self.to_install.is_some() { + action += 1; + } + if self.to_upgrade.is_some() { + action += 1; + } + if self.to_downgrade.is_some() { + action += 1; + } + if self.to_remove.is_some() { + action += 1; + } + if self.un_needed.is_some() { + action += 1; + } + if self.to_reinstall.is_some() { + action += 1; + } + if self.obsoleted.is_some() { + action += 1; + } + action + } + pub fn get_need_download(&self) -> u32 { + let mut download = 0; + if self.to_install.is_some() { + download += 1; + } + if self.to_upgrade.is_some() { + download += 1; + } + if self.to_downgrade.is_some() { + download += 1; + } + if self.to_reinstall.is_some() { + download += 1; + } + download + } +} +impl SolvedPkgInfo { + pub fn default() -> Self { + SolvedPkgInfo { + need_action: 0, + need_download: 0, + not_available: None, + existing: None, + not_resolved: Vec::new(), + not_installed: None, + base: SolvedPkgInfoBase::default(), + } + } +} diff --git a/rdnf/src/gpgcheck.rs b/rdnf/src/gpgcheck.rs new file mode 100644 index 00000000..68e554e5 --- /dev/null +++ b/rdnf/src/gpgcheck.rs @@ -0,0 +1,187 @@ +use std::{ffi::CString, fs::File, io::Read}; + +use crate::{ + errors::{ERROR_RDNF_NO_GPGKEY_CONF_ENTRY, ERROR_RDNF_OPERATION_ABORTED, ERROR_RDNF_INVALID_PUBKEY_FILE, ERROR_RDNF_RPMTS_CREATE_FAILED, ERROR_RDNF_RPMTD_CREATE_FAILED, ERROR_RDNF_RPM_GET_RSAHEADER_FAILED, ERROR_RDNF_RPM_GPG_PARSE_FAILED, ERROR_RDNF_RPM_GPG_NO_MATCH}, + rpm_trans::{RpmTs, RPMVSF_MASK_NOSIGNATURES}, + sub_command::{install::is_remote_url, repo::RepoData}, + Rdnf, cli::AlterOption, c_lib::{char_ptr_offset}, +}; +use anyhow::{bail, Result}; +use dialoguer::{theme::ColorfulTheme, Confirm}; +use rpm_sys::ffi::{ + rpmRC_e_RPMRC_NOKEY, rpmRC_e_RPMRC_NOTTRUSTED, rpmReadPackageFile, Fclose, Fopen, Header, rpmts, pgpParsePkts, pgpArmor_e_PGPARMOR_PUBKEY, rpmtsImportPubkey, rpmtsGetKeyring, rpmKeyring_s, rpmPubkeyNew, rpmPubkeyDig, rpmKeyringLookup, rpmRC_e_RPMRC_OK, rpmKeyringAddKey, rpmtsCreate, rpmtsSetVSFlags, rpmtdNew, headerConvert, headerConvOps_e_HEADERCONV_RETROFIT_V3, headerGet, rpmTag_e_RPMTAG_RSAHEADER, headerGetFlags_e_HEADERGET_MINMEM, pgpNewDig, pgpPrtPkts, pgpFreeDig, headerFree, rpmtdFree, rpmtsFree, +}; + +impl Rdnf { + pub fn gpgcheck_pkg(&self, rpm_ts: &mut RpmTs, file_path: &str, repo: &RepoData,alter_args:&AlterOption) -> Result<(Header,bool)> { + let mut gpg_sig_check = false; + let mut url_gpg_keys = None; + if !(alter_args.no_gpg_check || alter_args.skip_signatures) { + if repo.base.gpgcheck { + gpg_sig_check = true; + if repo.details.url_gpg_keys.is_some() { + url_gpg_keys = repo.details.url_gpg_keys.clone(); + } + } + } + let file_path_c = CString::new(file_path).unwrap(); + let fmode = CString::new("r.ufdio").unwrap(); + let fd = unsafe { Fopen(file_path_c.as_ptr(), fmode.as_ptr()) }; + let mut rpm_header = 0 as Header; + let rpm_rc = + unsafe { rpmReadPackageFile(rpm_ts.ts, fd, file_path_c.as_ptr(), &mut rpm_header) }; + if (rpm_rc == rpmRC_e_RPMRC_NOTTRUSTED || rpm_rc == rpmRC_e_RPMRC_NOKEY) && gpg_sig_check { + if url_gpg_keys.is_none() { + bail!(ERROR_RDNF_NO_GPGKEY_CONF_ENTRY); + } + let mut matched=0; + for url in url_gpg_keys.unwrap() { + let prompt = format!("Importing key from {}, is this ok", url.as_str()); + if !Confirm::with_theme(&ColorfulTheme::default()) + .with_prompt(prompt) + .interact() + .unwrap() + { + bail!(ERROR_RDNF_OPERATION_ABORTED); + }; + let key_local_path = if is_remote_url(url.as_str()) { + self.download_key_to_cache(url.as_str(), repo)? + } else { + match url.split_once("file://") { + Some((_, rest)) => "/".to_string() + rest.trim_start_matches("/"), + None => url, + } + }; + import_gpgkey_file(rpm_ts.ts,key_local_path.as_str())?; + let key_ring=unsafe{rpmtsGetKeyring(rpm_ts.ts, 0)}; + if gpgcheck(key_ring,key_local_path.as_str(),file_path)?{ + matched+=1; + } + } + if matched==0{ + bail!(ERROR_RDNF_RPM_GPG_NO_MATCH); + } + unsafe { rpmReadPackageFile(rpm_ts.ts, fd, file_path_c.as_ptr(), &mut rpm_header) }; + } + unsafe { Fclose(fd) }; + Ok((rpm_header,gpg_sig_check)) + } +} +pub fn import_gpgkey_file(ts:rpmts,file_path:&str)->Result<()>{ + let mut file=File::open(file_path)?; + let mut buffer=String::new(); + file.read_to_string(&mut buffer)?; + let data_size=buffer.len(); + let mut offset=0; + let buf = CString::new(buffer).unwrap(); + let mut pkt_ptr=0 as *mut u8; + let mut pkt_len=0 as u64; + let mut keys=0; + while (offset as usize) < data_size { + unsafe{ + let buf_ptr=char_ptr_offset(buf.as_ptr(), offset); + let armor_res=pgpParsePkts(buf_ptr,&mut pkt_ptr as *mut *mut u8,&mut pkt_len as *mut u64); + if armor_res==pgpArmor_e_PGPARMOR_PUBKEY{ + if rpmtsImportPubkey(ts, pkt_ptr, pkt_len)!=0{ + bail!(ERROR_RDNF_INVALID_PUBKEY_FILE); + } + keys+=1; + } + offset+=pkt_len as i32; + + }; + } + if keys==0{ + bail!(ERROR_RDNF_INVALID_PUBKEY_FILE); + } + Ok(()) +} +pub fn gpgcheck(key_ring:*mut rpmKeyring_s,key_file_path:&str,pkg_file:&str)->Result{ + add_key_file_to_keyring(key_ring,key_file_path)?; + Ok(verify_rpm_sig(key_ring,pkg_file)?) +} +pub fn add_key_file_to_keyring(key_ring:*mut rpmKeyring_s,key_file_path:&str)->Result<()>{ + let mut file=File::open(key_file_path)?; + let mut buffer=String::new(); + file.read_to_string(&mut buffer)?; + let data_size=buffer.len(); + let mut offset=0; + let buf = CString::new(buffer).unwrap(); + let mut pkt_ptr=0 as *mut u8; + let mut pkt_len=0 as u64; + let mut keys=0; + while (offset as usize) < data_size { + unsafe{ + let buf_ptr=char_ptr_offset(buf.as_ptr(), offset); + let armor_res=pgpParsePkts(buf_ptr,&mut pkt_ptr as *mut *mut u8,&mut pkt_len as *mut u64); + if armor_res==pgpArmor_e_PGPARMOR_PUBKEY{ + let pubkey=rpmPubkeyNew(pkt_ptr, pkt_len); + if pubkey.is_null() { + bail!(ERROR_RDNF_INVALID_PUBKEY_FILE) + } + let sig=rpmPubkeyDig(pubkey); + if sig.is_null() { + bail!(ERROR_RDNF_INVALID_PUBKEY_FILE); + } + if rpmKeyringLookup(key_ring, sig) !=rpmRC_e_RPMRC_OK{ + rpmKeyringAddKey(key_ring, pubkey); + }; + keys+=1; + } + offset+=pkt_len as i32; + + }; + } + if keys==0{ + bail!(ERROR_RDNF_INVALID_PUBKEY_FILE); + } + Ok(()) +} +pub fn verify_rpm_sig(key_ring:*mut rpmKeyring_s,pkg_file:&str)->Result{ + let pkg_file=CString::new(pkg_file).unwrap(); + let mode=CString::new("r.fdio").unwrap(); + let ts=unsafe{rpmtsCreate()}; + if ts.is_null() { + bail!(ERROR_RDNF_RPMTS_CREATE_FAILED); + } + unsafe{rpmtsSetVSFlags(ts, RPMVSF_MASK_NOSIGNATURES)}; + let td=unsafe{rpmtdNew()}; + if td.is_null() { + bail!(ERROR_RDNF_RPMTD_CREATE_FAILED); + } + let fd=unsafe{Fopen(pkg_file.as_ptr(), mode.as_ptr())}; + let b=unsafe{ + let mut header=0 as Header; + rpmReadPackageFile(ts, fd, pkg_file.as_ptr(), &mut header); + if headerConvert(header, headerConvOps_e_HEADERCONV_RETROFIT_V3 as i32)==0{ + bail!(ERROR_RDNF_RPM_GET_RSAHEADER_FAILED); + }; + if headerGet(header, rpmTag_e_RPMTAG_RSAHEADER, td, headerGetFlags_e_HEADERGET_MINMEM)==0{ + bail!(ERROR_RDNF_RPM_GET_RSAHEADER_FAILED); + }; + let digest=pgpNewDig(); + if pgpPrtPkts((*td).data as *const u8, (*td).count as u64, digest, 0) !=0{ + bail!(ERROR_RDNF_RPM_GPG_PARSE_FAILED); + } + let b=rpmKeyringLookup(key_ring, digest)==rpmRC_e_RPMRC_OK; + if !digest.is_null() { + pgpFreeDig(digest); + } + if !header.is_null() { + headerFree(header); + } + b + }; + unsafe{ + if !fd.is_null() { + Fclose(fd); + } + if !td.is_null() { + rpmtdFree(td); + } + if !ts.is_null() { + rpmtsFree(ts); + } + } + Ok(b) +} \ No newline at end of file diff --git a/rdnf/src/history.rs b/rdnf/src/history.rs new file mode 100644 index 00000000..3f096093 --- /dev/null +++ b/rdnf/src/history.rs @@ -0,0 +1,29 @@ +use std::{path::Path, fs::OpenOptions}; +use rusqlite::Connection; + +use crate::{Rdnf, default::{DEFAULT_DATA_LOCATION, HISTORY_DB_FILE}}; + +pub struct HistoryCtx{ + +} +impl HistoryCtx{ + pub fn new(path:&str)->Self{ + let db=Connection::open(path)?; + // "transactions" + + } + +} +impl Rdnf{ + pub fn get_history_ctx(&self)->Result{ + let history_db_path=self.rc.cli.installroot.trim_end_matches("/").to_string() + +DEFAULT_DATA_LOCATION.trim_end_matches("/")+"/"+HISTORY_DB_FILE; + if !Path::new(history_db_path.as_str()).exists(){ + OpenOptions::new().create(true).open(history_db_path.as_str())?; + }; + } + +} +pub fn db_table_exists(db:&Connection){ + +} \ No newline at end of file diff --git a/rdnf/src/i18n.rs b/rdnf/src/i18n.rs new file mode 100644 index 00000000..70f0aff2 --- /dev/null +++ b/rdnf/src/i18n.rs @@ -0,0 +1,73 @@ +#[cfg(feature="en_US")] +pub mod repo_list{ + pub const REPOLIST_REPO_ID:&str="repo id"; + pub const REPOLIST_REPO_NAME:&str="repo name"; + pub const REPOLIST_REPO_STATUS:&str="status"; + pub const REPOLIST_REPO_STATUS_ENABLED:&str="enabled"; + pub const REPOLIST_REPO_STATUS_DISABLED:&str="disabled"; + +} +#[cfg(feature="zh_CN")] +pub mod repo_list{ + pub const REPOLIST_REPO_ID:&str="仓库 id"; + pub const REPOLIST_REPO_NAME:&str="仓库名称"; + pub const REPOLIST_REPO_STATUS:&str="状态"; + pub const REPOLIST_REPO_STATUS_ENABLED:&str="启用"; + pub const REPOLIST_REPO_STATUS_DISABLED:&str="禁用"; +} + +#[cfg(feature="en_US")] +pub mod action_alter{ + pub const ACTION_ALTER_INTALL:&str="Installing"; + pub const ACTION_ALTER_UPGRADE:&str="Upgrading"; + pub const ACTION_ALTER_ERASE:&str="Removing"; + pub const ACTION_ALTER_DOWNGRADE:&str="Downgrading"; + pub const ACTION_ALTER_REINSTALL:&str="Reinstalling"; + pub const ACTION_ALTER_OBSOLETED:&str="Obsoleting"; +} +#[cfg(feature="zh_CN")] +pub mod action_alter{ + pub const ACTION_ALTER_INTALL:&str="安装"; + pub const ACTION_ALTER_UPGRADE:&str="升级"; + pub const ACTION_ALTER_ERASE:&str="卸载"; + pub const ACTION_ALTER_DOWNGRADE:&str="降级"; + pub const ACTION_ALTER_REINSTALL:&str="重新安装"; + pub const ACTION_ALTER_OBSOLETED:&str="废弃"; +} + + +#[cfg(feature="en_US")] +pub mod pkg_info{ + pub const PKG_INFO_NAME___:&str="Name "; + pub const PKG_INFO_ARCH___:&str="Arch "; + pub const PKG_INFO_EPOCH__:&str="Epoch "; + pub const PKG_INFO_VERSION:&str="Version "; + pub const PKG_INFO_RELEASE:&str="Release "; + pub const PKG_INFO_SIZE___:&str="Size "; + pub const PKG_INFO_REPO___:&str="Repo "; + pub const PKG_INFO_SUMMARY:&str="Summary "; + pub const PKG_INFO_URL____:&str="URL "; + pub const PKG_INFO_LICENSE:&str="License "; + pub const PKG_INFO_DESC___:&str="Description"; +} +#[cfg(feature="zh_CN")] +pub mod pkg_info{ + pub const PKG_INFO_NAME___:&str="名称 "; + pub const PKG_INFO_ARCH___:&str="架构 "; + pub const PKG_INFO_EPOCH__:&str="时期 "; + pub const PKG_INFO_VERSION:&str="版本 "; + pub const PKG_INFO_RELEASE:&str="发布 "; + pub const PKG_INFO_SIZE___:&str="大小 "; + pub const PKG_INFO_REPO___:&str="仓库 "; + pub const PKG_INFO_SUMMARY:&str="概况 "; + pub const PKG_INFO_URL____:&str="URL "; + pub const PKG_INFO_LICENSE:&str="协议 "; + pub const PKG_INFO_DESC___:&str="描述 "; +} + + + + + + + diff --git a/rdnf/src/lock.rs b/rdnf/src/lock.rs new file mode 100644 index 00000000..46b7550d --- /dev/null +++ b/rdnf/src/lock.rs @@ -0,0 +1,102 @@ +use std::{ + fs::{File, OpenOptions}, + str::FromStr, +}; + +use anyhow::{bail, Result}; +use rustix::fd::AsRawFd; + +pub enum RdnfFlockMode { + READ, + WriteRead, + Wait, +} +pub struct Rdnflock { + fd: File, + pub openmode: RdnfFlockMode, + pub path: String, + pub descr: String, + fdrefs: usize, +} +pub fn flock_new(lock_path: &str, descr: &str) -> Result { + let flock = match OpenOptions::new() + .read(true) + .write(true) + .create(true) + .open(lock_path) + { + Ok(file) => Rdnflock { + fd: file, + openmode: RdnfFlockMode::WriteRead, + path: String::from_str(lock_path).unwrap(), + descr: String::from_str(descr).unwrap(), + fdrefs: 1, + }, + Err(_) => match OpenOptions::new().read(true).open(lock_path) { + Ok(file) => Rdnflock { + fd: file, + openmode: RdnfFlockMode::READ, + path: String::from_str(lock_path).unwrap(), + descr: String::from_str(descr).unwrap(), + fdrefs: 1, + }, + Err(_) => { + bail!("can't create {} lock on {}", lock_path, descr) + } + }, + }; + Ok(flock) +} +pub fn flock_acquire(lock: &mut Rdnflock, mode: RdnfFlockMode) -> Result { + let mut res: bool = false; + if lock.fdrefs > 1 { + res = true + } else { + let cmd: i32 = match mode { + RdnfFlockMode::Wait => libc::F_SETLKW, + _ => libc::F_SETLK, + }; + let l_type = match mode { + RdnfFlockMode::READ => libc::F_RDLCK, + RdnfFlockMode::WriteRead => libc::F_RDLCK, + _ => libc::F_WRLCK, + }; + let info = libc::flock { + l_type: l_type as i16, + l_whence: libc::SEEK_SET as i16, + l_start: 0, + l_len: 0, + l_pid: 0, + }; + unsafe { + if libc::fcntl(lock.fd.as_raw_fd(), cmd, &info) != -1 { + res = true; + }; + } + } + lock.fdrefs += res as usize; + Ok(res) +} +pub fn flock_release(lock: &mut Rdnflock) { + if lock.fdrefs == 2 { + let info = libc::flock { + l_type: libc::F_UNLCK as i16, + l_whence: libc::SEEK_SET as i16, + l_start: 0, + l_len: 0, + l_pid: 0, + }; + unsafe { + libc::fcntl(lock.fd.as_raw_fd(), libc::F_SETLK, &info); + } + } +} +#[cfg(test)] +mod tests { + #[test] + fn test_flock() { + // let c=umask; + // let t=c.to_string(); + // println!("c {}",t); + } +} diff --git a/rdnf/src/main.rs b/rdnf/src/main.rs new file mode 100644 index 00000000..c7132dc4 --- /dev/null +++ b/rdnf/src/main.rs @@ -0,0 +1,141 @@ +use anyhow::Result; +use clap::Parser; +use console::Term; +use indicatif::MultiProgress; +use solv_sys::ffi::Repo; + +use cli::{rpm_init, Cli, Commands}; +use conf::ConfigMain; + +use solv::sack::Solvsack; +use sub_command::{ + install::AlterType, + repo::{init_cmdline_repo, load_repo_data, repo_list_finalize, RepoData, RepoListFilter}, +}; +use utils::is_already_running; + +use crate::utils::check_root; + +mod sub_command; + +mod c_lib; +mod cli; +mod conf; +mod default; +mod errors; +mod goal; +mod gpgcheck; +mod i18n; +mod lock; +mod metalink; +mod output; +mod pkgutils; +mod repomd; +mod rpm_trans; +mod solv; +mod utils; +#[derive(Debug, Clone)] +pub struct Rdnf { + rc: RdnfContext, + repos: Vec, + solv_cmdline_repo: *mut Repo, +} +#[derive(Debug, Clone)] +pub struct RdnfContext { + sack: Solvsack, + cli: Cli, + conf: ConfigMain, + multi_process: MultiProgress, + term: Term, +} +impl Rdnf { + pub fn new() -> Result { + is_already_running()?; + rpm_init()?; + let mut cli = Cli::parse().init()?; + let conf = ConfigMain::from(&mut cli)?; + let mut sack = Solvsack::from(&conf, &cli)?; + let mut repos = load_repo_data(&conf, RepoListFilter::All)?; + repos.sort_by(|a, b| a.base.priority.cmp(&(b.base.priority))); + repos.sort_by(|a, b| a.psz_id.cmp(&b.psz_id)); + repo_list_finalize(&mut cli, &conf, &mut repos)?; + let solv_cmdline_repo = init_cmdline_repo(&mut sack)?; + let term = Term::stdout(); + let multi_process = MultiProgress::new(); + let rc = RdnfContext { + sack, + cli, + conf, + multi_process, + term, + }; + let rdnf = Rdnf { + rc, + repos, + solv_cmdline_repo, + }; + Ok(rdnf) + } + pub fn refresh_sack() -> Result<()> { + Ok(()) + } +} + +impl AsRef for Rdnf { + #[inline] + fn as_ref(&self) -> &Self { + self + } +} +// #[tokio::main(flavor = "current_thread")] +fn main() -> Result<()> { + let mut rdnf = Rdnf::new()?; + match &rdnf.rc.cli.command { + Commands::Repolist(_) => { + rdnf.repo_list()?; + } + Commands::Makecache => { + rdnf.rc.cli.refresh = true; + rdnf.make_cache()?; + } + Commands::Search { pkgs } => { + let pkgs = pkgs.clone(); + rdnf.search_pkg(pkgs)?; + } + Commands::Install(alter) => { + check_root()?; + let pkgs = alter.pkgs.clone(); + let alter = alter.clone(); + rdnf.alter_command(pkgs, AlterType::Install, &alter)?; + } + Commands::Remove(alter)=>{ + check_root()?; + let pkgs=alter.pkgs.clone(); + let alter=alter.clone(); + rdnf.alter_command(pkgs, AlterType::Erase, &alter)?; + } + Commands::Reinstall(alter) => { + check_root()?; + let pkgs=alter.pkgs.clone(); + let alter=alter.clone(); + rdnf.alter_command(pkgs, AlterType::ReInstall, &alter)?; + + }, + Commands::Update(alter) => { + check_root()?; + let pkgs = alter.pkgs.clone(); + let alter=alter.clone(); + if pkgs.is_empty(){ + rdnf.alter_command(pkgs, AlterType::UpgradeAll, &alter)?; + }else{ + rdnf.alter_command(pkgs, AlterType::Upgrade, &alter)?; + } + }, + Commands::Info(info_opt) => { + rdnf.info_command(info_opt.clone())?; + + }, + + } + Ok(()) +} diff --git a/rdnf/src/metalink.rs b/rdnf/src/metalink.rs new file mode 100644 index 00000000..e3f0a210 --- /dev/null +++ b/rdnf/src/metalink.rs @@ -0,0 +1,171 @@ +use std::{fs::File, io::Read}; + +use anyhow::{bail, Result}; +use quick_xml::{events::Event, Reader}; + +use crate::sub_command::repoutils::HashKind; +#[derive(Debug, Clone)] +pub struct MetalinkHashInfo { + pub kind: HashKind, + pub value: String, +} +#[derive(Debug, Clone)] +pub struct MetalinkUrlInfo { + pub protocol: String, + pub kind: String, + pub location: String, + pub preference: i32, + pub url: String, +} +#[derive(Debug, Clone)] +pub struct MetalinkContext { + pub filename: String, + pub timestamp: u128, + pub size: u128, + pub hashs: Vec, + pub urls: Vec, +} +impl MetalinkContext { + pub fn from(path: &str) -> Result> { + let mut buffer = String::new(); + File::open(path).unwrap().read_to_string(&mut buffer)?; + let mut reader = Reader::from_str(buffer.as_str()); + reader.trim_text(true); + let mut files = Vec::new(); + loop { + match reader.read_event() { + Ok(Event::Start(e)) => { + if e.name().as_ref() == b"file" { + for attr in e.attributes() { + let attr = attr?; + if String::from_utf8_lossy(attr.key.as_ref()) == "name" { + let mut mc = MetalinkContext { + filename: String::from_utf8_lossy(attr.value.as_ref()) + .to_string(), + timestamp: 0, + size: 0, + hashs: Vec::new(), + urls: Vec::new(), + }; + loop { + match reader.read_event()? { + Event::Start(ele) => match ele.name().as_ref() { + b"size" => { + mc.size = reader + .read_text(ele.name())? + .parse::()?; + } + b"hash" => { + for attr in ele.attributes() { + let attr = attr?; + if String::from_utf8_lossy(attr.key.as_ref()) + == "type" + { + let hash_info = MetalinkHashInfo { + kind: HashKind::from( + String::from_utf8_lossy( + attr.value.as_ref(), + ) + .to_string() + .as_str(), + ), + value: reader + .read_text(ele.name())? + .to_string(), + }; + mc.hashs.push(hash_info); + } + } + } + b"url" => { + let mut protocol = String::from("https"); + let mut kind = String::from("https"); + let mut location = String::from("US"); + let mut preference = 100; + for attr in ele.attributes() { + let attr = attr?; + let key = + String::from_utf8_lossy(attr.key.as_ref()); + let value = String::from_utf8_lossy( + attr.value.as_ref(), + ); + if key == "protocol" { + protocol = value.to_string(); + } else if key == "type" { + kind = value.to_string(); + } else if key == "location" { + location = value.to_string(); + } else if key == "preference" { + preference = value.parse::()?; + } + } + let url = reader.read_text(ele.name())?.to_string(); + let url_info = MetalinkUrlInfo { + protocol, + kind, + location, + preference, + url, + }; + if url_info + .protocol + .matches("http") + .collect::>() + .len() + >= 1 + { + mc.urls.push(url_info); + } + } + _ => { + if String::from_utf8_lossy(ele.name().as_ref()) + .matches("timestamp") + .collect::>() + .len() + >= 1 + { + mc.timestamp = reader + .read_text(ele.name())? + .parse::()?; + } + } + }, + Event::End(ele) => { + if ele.name().as_ref() == b"file" { + break; + } + } + _ => {} + } + } + mc.urls.sort_by(|a, b| b.preference.cmp(&a.preference)); + files.push(mc); + } + } + } + } + Ok(Event::Eof) => break, // exits the loop when reaching end of file + Err(e) => panic!("Error at position {}: {:?}", reader.buffer_position(), e), + _ => (), // There are several other `Event`s we do not consider here + } + } + Ok(files) + } + pub fn from_with_filename(path: &str, name: &str) -> Result { + let vec = Self::from(path)?; + let mut t = None; + for ele in vec { + if ele.filename == name { + t = Some(ele); + break; + } + } + let t = match t { + Some(t) => t, + None => { + bail!("check metalink url") + } + }; + Ok(t) + } +} diff --git a/rdnf/src/output.rs b/rdnf/src/output.rs new file mode 100644 index 00000000..c40390e4 --- /dev/null +++ b/rdnf/src/output.rs @@ -0,0 +1,149 @@ +use console::{style, Term}; + +use crate::{ + errors::ERROR_RDNF_INVALID_PARAMETER, + goal::{SolvedPkgInfo}, + i18n::action_alter::{ + ACTION_ALTER_DOWNGRADE, ACTION_ALTER_ERASE, ACTION_ALTER_INTALL, ACTION_ALTER_OBSOLETED, + ACTION_ALTER_REINSTALL, ACTION_ALTER_UPGRADE, + }, + sub_command::{install::AlterType, info::PkgInfo}, + utils::format_size, +}; +use anyhow::{bail, Result}; +impl SolvedPkgInfo { + pub fn print(&self, term: &Term) -> Result<()> { + if self.existing.is_some() { + for pkg_info in self.existing.as_ref().unwrap() { + let base = &pkg_info.base; + let name = format!( + "{}-{}-{}.{}", + style(base.name.as_str()).green(), + base.version, + base.release, + base.arch + ); + term.write_line(&format!("Package {} is already installed, skipping", name))?; + } + } + if self.not_available.is_some() { + for pkg_info in self.not_available.as_ref().unwrap() { + term.write_line(&format!( + "No package {} available.", + style(pkg_info.base.name.as_str()).red() + ))?; + } + } + if self.base.to_install.is_some() { + PkgInfo::print_action( + self.base.to_install.as_ref().unwrap(), + term, + AlterType::Install, + )?; + } + if self.base.to_upgrade.is_some() { + PkgInfo::print_action( + self.base.to_upgrade.as_ref().unwrap(), + term, + AlterType::Upgrade, + )?; + } + if self.base.to_downgrade.is_some() { + PkgInfo::print_action( + self.base.to_downgrade.as_ref().unwrap(), + term, + AlterType::DownGrade, + )?; + } + if self.base.to_remove.is_some() { + PkgInfo::print_action( + self.base.to_remove.as_ref().unwrap(), + term, + AlterType::Erase, + )?; + } + if self.base.un_needed.is_some() { + PkgInfo::print_action( + self.base.un_needed.as_ref().unwrap(), + term, + AlterType::Erase, + )?; + } + if self.base.to_reinstall.is_some() { + PkgInfo::print_action( + self.base.to_reinstall.as_ref().unwrap(), + term, + AlterType::ReInstall, + )?; + } + if self.base.obsoleted.is_some() { + PkgInfo::print_action( + self.base.obsoleted.as_ref().unwrap(), + term, + AlterType::Obsoleted, + )?; + } + Ok(()) + } +} +impl PkgInfo { + pub fn print_action(pkg_infos: &Vec, term: &Term, alter_type: AlterType) -> Result<()> { + term.write_line((alter_type.to_str()?.to_string() + ":").as_str())?; + let (_, width) = term.size(); + let width_float = width as f32; + let name_col = (width_float * 0.3) as usize; + let arch_col = (width_float * 0.15) as usize; + let evr_col = (width_float * 0.25) as usize; + let repo_col = (width_float * 0.15) as usize; + let install_size_col = (width_float * 0.15) as usize; + let mut total_size = 0; + for pkg_info in pkg_infos { + let base = &pkg_info.base; + let evr = if base.epoch == 0 { + format!("{}-{}", base.version, base.release) + } else { + format!("{}:{}-{}", base.epoch, base.version, base.release) + }; + total_size += pkg_info.details.install_size; + let item = format!( + "{: Result<&str> { + let p = match self { + Self::Install => ACTION_ALTER_INTALL, + Self::Upgrade => ACTION_ALTER_UPGRADE, + Self::Erase => ACTION_ALTER_ERASE, + Self::DownGrade => ACTION_ALTER_DOWNGRADE, + Self::ReInstall => ACTION_ALTER_REINSTALL, + Self::Obsoleted => ACTION_ALTER_OBSOLETED, + _ => { + bail!(ERROR_RDNF_INVALID_PARAMETER) + } + }; + Ok(p) + } +} diff --git a/rdnf/src/pkgutils.rs b/rdnf/src/pkgutils.rs new file mode 100644 index 00000000..fd3772aa --- /dev/null +++ b/rdnf/src/pkgutils.rs @@ -0,0 +1,176 @@ +use std::ffi::CStr; + +use anyhow::{bail, Result}; +use solv_sys::ffi::{Queue}; + +use crate::{ + c_lib::{pool_id2solvable, queue_push}, + default::RDNF_NAME, + errors::{ERROR_RDNF_NO_DATA, ERROR_RDNF_NO_MATCH, ERROR_RDNF_SELF_ERASE}, + goal::{SolvedPkgInfoBase}, + solv::{sack::Solvsack, SolvPackageList}, + Rdnf, sub_command::info::{PkgInfo, PkgInfoLevel}, +}; + +impl Solvsack { + pub fn get_glob_pkgs(&self,pkg_glob:&str,queue_goal: *mut Queue)->Result<()>{ + let pkg_list=self.solv_find_available_pkg_by_name(pkg_glob)?; + if pkg_list.get_size() >0{ + for index in 0..pkg_list.get_size(){ + let id=pkg_list.get_pkg_id(index); + queue_push(queue_goal, id); + } + } + Ok(()) + } + pub fn add_pkgs_for_install(&self, queue_goal: *mut Queue, pkg_name: &str) -> Result { + let highest_id = self.solv_find_highest_available(pkg_name)?; + if self.verify_install_pkg(highest_id)? { + queue_push(queue_goal, highest_id); + Ok(true) + } else { + Ok(false) + } + } + pub fn add_pkgs_for_upgrade(&self,queue_goal: *mut Queue,pkg_name: &str)->Result{ + let highest_id=self.solv_find_highest_available(pkg_name)?; + if self.verify_upgrade_pkg(highest_id)?{ + queue_push(queue_goal, highest_id); + Ok(true) + }else{ + Ok(false) + } + } + pub fn add_pkgs_for_erase(&self,queue_goal: *mut Queue,pkg_name: &str)->Result{ + match self.solv_find_installed_pkg_by_name(pkg_name) { + Ok(install_pkg_list) => { + let count = install_pkg_list.get_size(); + for index in 0..count{ + let id=install_pkg_list.get_pkg_id(index); + queue_push(queue_goal, id); + } + }, + Err(_) => {bail!("Package {} don't be installed,can't be removed",pkg_name)}, + }; + Ok(true) + } + pub fn add_pkgs_for_reinstall(&self,queue_goal: *mut Queue,pkg_name: &str)->Result<()>{ + match self.solv_find_installed_pkg_by_name(pkg_name) { + Ok(install_pkg_list) => { + let installed_id=install_pkg_list.get_pkg_id(0); + let nevr=self.solv_get_pkg_nevr_by_id(installed_id)?; + let available_pkg_list=self.solv_find_available_pkg_by_name(nevr.as_str())?; + let available_pkg_id=available_pkg_list.get_pkg_id(0); + queue_push(queue_goal, available_pkg_id); + }, + Err(_) => {bail!("Package {} don't be installed,can't be reinstalled",pkg_name)}, + }; + Ok(()) + } + + pub fn verify_install_pkg(&self, id: i32) -> Result { + let pkg_name = self.solv_get_pkg_name_by_id(id)?; + let installed_id = match self.solv_find_highest_installed(pkg_name.as_str()) { + Ok(s) => s, + Err(_) => { + return Ok(true); + } + }; + let evr_cmp = self.solv_cmp_evr(id, installed_id)?; + Ok(evr_cmp != 0) + } + pub fn verify_upgrade_pkg(&self,id:i32)->Result{ + let pkg_name=self.solv_get_pkg_name_by_id(id)?; + let intalled_id=match self.solv_find_highest_installed(pkg_name.as_str()) { + Ok(s) => {s}, + Err(_) => {return Ok(true);}, + }; + let result=match self.solv_cmp_evr(id, intalled_id) { + Ok(evr) => {evr >0}, + Err(_) => {true}, + }; + Ok(result) + } + pub fn solv_get_pkg_reponame_by_id(&self, pkg_id: i32) -> Result<&str> { + let solv = pool_id2solvable(self.pool, pkg_id); + if solv.is_null() { + bail!(ERROR_RDNF_NO_DATA); + }; + let name_ptr = unsafe { (*(*solv).repo).name }; + if name_ptr.is_null() { + bail!(ERROR_RDNF_NO_DATA); + }; + Ok(unsafe { CStr::from_ptr(name_ptr).to_str()? }) + } +} +impl PkgInfo { + pub fn populate_pkg_info(sack: &Solvsack, pkg_list: &SolvPackageList,level:PkgInfoLevel) -> Result> { + let count = pkg_list.get_size(); + if count == 0 { + bail!(ERROR_RDNF_NO_MATCH); + }; + let mut pkginfos = Vec::new(); + for index in 0..count { + let pkg_id = pkg_list.get_pkg_id(index); + match level { + _ => {}, + } + let base = sack.solv_get_pkginfo_base_by_id(pkg_id)?; + let details = sack.solv_get_pkginfo_details_by_id(pkg_id)?; + let other=match level { + PkgInfoLevel::Other => { + Some(sack.solv_get_pkginfo_other_by_id(pkg_id)?) + }, + _=>{None} + }; + pkginfos.push(PkgInfo { + base, + details, + other, + }); + } + Ok(pkginfos) + } +} +impl Rdnf { + pub fn pkgs_to_exclude(&self) -> Result> { + let mut count = 0; + let mut exclude_pkgs = Vec::new(); + if !self.rc.cli.disable_excludes && self.rc.conf.excludepkgs.is_some() { + println!("Warning: The following packages are excluded from rdnf.conf"); + for ele in self.rc.conf.excludepkgs.as_ref().unwrap() { + print!("{}/t", ele); + exclude_pkgs.push(ele.clone()); + count += 1; + if count % 3 == 0 { + print!("\n"); + } + } + } + if !self.rc.cli.disable_excludes && self.rc.cli.exclude.is_some() { + for ele in self.rc.cli.exclude.as_ref().unwrap() { + exclude_pkgs.push(ele.clone()); + } + } + Ok(exclude_pkgs) + } +} +impl SolvedPkgInfoBase { + pub fn check_protected_pkgs(&self) -> Result<()> { + if self.to_remove.is_some() { + for pkg_info in self.to_remove.as_ref().unwrap() { + if pkg_info.base.name == RDNF_NAME { + bail!(ERROR_RDNF_SELF_ERASE) + } + } + } + if self.obsoleted.is_some() { + for pkg_info in self.obsoleted.as_ref().unwrap() { + if pkg_info.base.name == RDNF_NAME { + bail!(ERROR_RDNF_SELF_ERASE) + } + } + } + Ok(()) + } +} diff --git a/rdnf/src/repomd.rs b/rdnf/src/repomd.rs new file mode 100644 index 00000000..7def6e0c --- /dev/null +++ b/rdnf/src/repomd.rs @@ -0,0 +1,199 @@ +use anyhow::{bail, Result}; +use quick_xml::{events::Event, Reader}; +use std::{ + fs::{metadata, File}, + io::Read, + path::Path, +}; + +use crate::{ + sub_command::{ + repo::RepoData, + repoutils::{download_file, HashKind}, + }, + RdnfContext, +}; +#[derive(Debug)] +pub struct RepoMd { + pub primary: Option, + pub filelists: Option, + pub updateinfo: Option, + pub other: Option, +} +#[derive(Debug)] +pub struct RepoMdItem { + pub checksum: (HashKind, String), + pub location: String, + pub size: u64, +} +impl RepoMdItem { + #[inline] + pub fn ensure_exists( + mut self, + rc: &RdnfContext, + repo: &RepoData, + prefix_path: &str, + ) -> Result { + let file_path = prefix_path.to_string() + self.location.as_str(); + if !Path::new(file_path.as_str()).exists() { + let url = repo.details.base_url.clone().unwrap()+"/" + self.location.as_str(); + let mut flag = false; + for _i in 1..10 { + download_file(rc, repo, url.as_str(), file_path.as_str(), &repo.psz_id)?; + if metadata(file_path.as_str())?.len() == self.size { + if self + .checksum + .0 + .clone() + .checksum(file_path.as_str(), self.checksum.1.as_str())? + { + flag = true; + break; + }; + } + } + if !flag { + bail!( + "Failed to download file {},or source file corrupted ", + url.as_str() + ); + } + } + self.location = file_path; + Ok(self) + } +} +impl RepoMd { + pub fn parse_from(path: &str) -> Result { + let mut buf = String::new(); + File::open(path)?.read_to_string(&mut buf)?; + let mut reader = Reader::from_str(buf.as_str()); + reader.trim_text(true); + let mut repomd = RepoMd { + primary: None, + filelists: None, + updateinfo: None, + other: None, + }; + loop { + match reader.read_event() { + Ok(Event::Start(data)) => match data.name().as_ref() { + b"data" => { + let mut checksum = (HashKind::Invalid, String::new()); + let mut location = String::new(); + let mut size = 0; + loop { + match reader.read_event()? { + Event::Empty(ele) => match ele.name().as_ref() { + b"location" => { + for attr in ele.attributes() { + let attr = attr?; + if String::from_utf8_lossy(attr.key.as_ref()) == "href" + { + location = + String::from_utf8_lossy(attr.value.as_ref()) + .to_string(); + } + } + } + _ => {} + }, + Event::Start(ele) => match ele.name().as_ref() { + b"checksum" => { + for attr in ele.attributes() { + let attr = attr?; + if String::from_utf8_lossy(attr.key.as_ref()) == "type" + { + let kind = + String::from_utf8_lossy(attr.value.as_ref()) + .to_string(); + checksum.0 = HashKind::from(kind.as_str()); + checksum.1 = + reader.read_text(ele.name())?.to_string(); + } + } + } + b"size" => { + size = reader.read_text(ele.name())?.parse::()?; + } + _ => {} + }, + Event::End(ele) => { + if ele.name().as_ref() == b"data" { + break; + } + } + _ => {} + } + } + let repo_md_item = RepoMdItem { + checksum, + location, + size, + }; + for attr_data in data.attributes() { + let attr_data = attr_data?; + if String::from_utf8_lossy(attr_data.key.as_ref()) == "type" { + match String::from_utf8_lossy(attr_data.value.as_ref()).as_bytes() { + b"primary" => repomd.primary = Some(repo_md_item), + b"filelists" => repomd.filelists = Some(repo_md_item), + b"updateinfo" => repomd.updateinfo = Some(repo_md_item), + b"other" => repomd.other = Some(repo_md_item), + _ => {} + } + break; + } + } + } + _ => {} + }, + Ok(Event::Eof) => break, + Err(e) => { + bail!( + "Failed to parse {} at position {}: {:?}", + path, + reader.buffer_position(), + e + ) + } + _ => {} + } + } + Ok(repomd) + } + pub fn ensure_repo_md_parts( + self, + rc: &RdnfContext, + repo: &RepoData, + cache_dir: String, + ) -> Result { + let mut repo_md = RepoMd { + primary: None, + filelists: None, + updateinfo: None, + other: None, + }; + + if let Some(primary) = self.primary { + repo_md.primary = Some(primary.ensure_exists(rc, repo, cache_dir.as_str())?); + } + + if !repo.base.skip_md_filelists { + if let Some(file_lists) = self.filelists { + repo_md.filelists = Some(file_lists.ensure_exists(rc, repo, cache_dir.as_str())?); + } + } + if !repo.base.skip_md_updateinfo { + if let Some(update_info) = self.updateinfo { + repo_md.updateinfo = + Some(update_info.ensure_exists(rc, repo, cache_dir.as_str())?); + } + } + if !repo.base.skip_md_other { + if let Some(other) = self.other { + repo_md.other = Some(other.ensure_exists(rc, repo, cache_dir.as_str())?); + } + } + Ok(repo_md) + } +} diff --git a/rdnf/src/rpm_trans.rs b/rdnf/src/rpm_trans.rs new file mode 100644 index 00000000..e3e54332 --- /dev/null +++ b/rdnf/src/rpm_trans.rs @@ -0,0 +1,484 @@ +use std::ffi::CStr; +use std::ffi::CString; +use std::path::Path; + +use anyhow::bail; +use anyhow::Result; +use console::style; +use console::Term; + +use libc::c_void; +use rpm_sys::ffi::fnpyKey; +use rpm_sys::ffi::rpmDbiTag_e_RPMDBI_LABEL; +use rpm_sys::ffi::rpmProblemFree; +use rpm_sys::ffi::rpmProblemGetStr; +use rpm_sys::ffi::rpmProblemGetType; +use rpm_sys::ffi::rpmProblemString; +use rpm_sys::ffi::rpmProblemType_e_RPMPROB_REQUIRES; +use rpm_sys::ffi::rpmRelocation; +use rpm_sys::ffi::rpmTag; +use rpm_sys::ffi::rpmVSFlags_e_RPMVSF_NODSA; +use rpm_sys::ffi::rpmVSFlags_e_RPMVSF_NODSAHEADER; +use rpm_sys::ffi::rpmVSFlags_e_RPMVSF_NOMD5; +use rpm_sys::ffi::rpmVSFlags_e_RPMVSF_NOPAYLOAD; +use rpm_sys::ffi::rpmVSFlags_e_RPMVSF_NORSA; +use rpm_sys::ffi::rpmVSFlags_e_RPMVSF_NORSAHEADER; +use rpm_sys::ffi::rpmVSFlags_e_RPMVSF_NOSHA1HEADER; +use rpm_sys::ffi::rpmVSFlags_e_RPMVSF_NOSHA256HEADER; +use rpm_sys::ffi::rpmdbFreeIterator; +use rpm_sys::ffi::rpmdbGetIteratorOffset; +use rpm_sys::ffi::rpmdbNextIterator; +use rpm_sys::ffi::rpmlogLvl_e_RPMLOG_ALERT; +use rpm_sys::ffi::rpmlogLvl_e_RPMLOG_CRIT; +use rpm_sys::ffi::rpmlogLvl_e_RPMLOG_DEBUG; +use rpm_sys::ffi::rpmlogLvl_e_RPMLOG_EMERG; +use rpm_sys::ffi::rpmlogLvl_e_RPMLOG_ERR; +use rpm_sys::ffi::rpmlogLvl_e_RPMLOG_INFO; +use rpm_sys::ffi::rpmlogLvl_e_RPMLOG_NOTICE; +use rpm_sys::ffi::rpmlogLvl_e_RPMLOG_WARNING; +use rpm_sys::ffi::rpmlogSetMask; +use rpm_sys::ffi::rpmprobFilterFlags_e_RPMPROB_FILTER_OLDPACKAGE; +use rpm_sys::ffi::rpmprobFilterFlags_e_RPMPROB_FILTER_REPLACEPKG; +use rpm_sys::ffi::rpmps; +use rpm_sys::ffi::rpmpsFreeIterator; +use rpm_sys::ffi::rpmpsGetProblem; +use rpm_sys::ffi::rpmpsInitIterator; +use rpm_sys::ffi::rpmpsNextIterator; +use rpm_sys::ffi::rpmpsNumProblems; +use rpm_sys::ffi::rpmteEVR; +use rpm_sys::ffi::rpmteN; +use rpm_sys::ffi::rpmtransFlags_e_RPMTRANS_FLAG_NONE; +use rpm_sys::ffi::rpmtransFlags_e_RPMTRANS_FLAG_NOSCRIPTS; +use rpm_sys::ffi::rpmtransFlags_e_RPMTRANS_FLAG_TEST; +use rpm_sys::ffi::rpmts; +use rpm_sys::ffi::rpmtsAddEraseElement; +use rpm_sys::ffi::rpmtsAddInstallElement; +use rpm_sys::ffi::rpmtsCheck; +use rpm_sys::ffi::rpmtsClean; +use rpm_sys::ffi::rpmtsCreate; +use rpm_sys::ffi::rpmtsInitIterator; +use rpm_sys::ffi::rpmtsOrder; +use rpm_sys::ffi::rpmtsProblems; +use rpm_sys::ffi::rpmtsRun; +use rpm_sys::ffi::rpmtsSetFlags; +use rpm_sys::ffi::rpmtsSetRootDir; +use rpm_sys::ffi::rpmtsSetVSFlags; +use rpm_sys::ffi::rpmtsSetVfyLevel; +use rpm_sys::ffi::rpmtsVSFlags; +use rpm_sys::ffi::rpmtsiInit; +use rpm_sys::ffi::rpmtsiNext; +use rpm_sys::ffi::rpmvercmp; +use rpm_sys::ffi::RPMLOG_PRIMASK; +use rpm_sys::ffi::RPMSIG_DIGEST_TYPE; +use rpm_sys::ffi::RPMSIG_SIGNATURE_TYPE; + +use crate::c_lib::set_callbackfunction; +use crate::cli::AlterOption; +use crate::default::RPM_CACHE_DIR_NAME; +use crate::errors::ERROR_RDNF_INVALID_PARAMETER; +use crate::errors::ERROR_RDNF_REPO_NOT_FOUND; +use crate::errors::ERROR_RDNF_RPMTS_CREATE_FAILED; +use crate::errors::ERROR_RDNF_RPM_CHECK; +use crate::errors::ERROR_RDNF_TRANSACTION_FAILED; +use crate::goal::SolvedPkgInfo; +use crate::sub_command::info::PkgInfo; +use crate::sub_command::install::AlterType; +use crate::Rdnf; +pub const RPMVSF_MASK_NODIGESTS: u32 = rpmVSFlags_e_RPMVSF_NOSHA1HEADER + | rpmVSFlags_e_RPMVSF_NOSHA256HEADER + | rpmVSFlags_e_RPMVSF_NOPAYLOAD + | rpmVSFlags_e_RPMVSF_NOMD5; + +pub const RPMVSF_MASK_NOSIGNATURES: u32 = rpmVSFlags_e_RPMVSF_NODSAHEADER + | rpmVSFlags_e_RPMVSF_NORSAHEADER + | rpmVSFlags_e_RPMVSF_NODSA + | rpmVSFlags_e_RPMVSF_NORSA; +pub const RPMSIG_VERIFIABLE_TYPE: u32 = RPMSIG_DIGEST_TYPE | RPMSIG_SIGNATURE_TYPE; +pub struct RpmTs { + pub cached_rpms: Vec, + pub trans_flags: i32, + pub prob_filter_flags: u32, + pub ts: rpmts, +} +impl Rdnf { + pub fn parse_rpm_verbosity(&self) -> u32 { + match self.rc.cli.rpm_verbosity.as_str() { + "emergency" => rpmlogLvl_e_RPMLOG_EMERG, + "alert" => rpmlogLvl_e_RPMLOG_ALERT, + "critical" => rpmlogLvl_e_RPMLOG_CRIT, + "error" => rpmlogLvl_e_RPMLOG_ERR, + "warning" => rpmlogLvl_e_RPMLOG_WARNING, + "notice" => rpmlogLvl_e_RPMLOG_NOTICE, + "info" => rpmlogLvl_e_RPMLOG_INFO, + "debug" => rpmlogLvl_e_RPMLOG_DEBUG, + _ => rpmlogLvl_e_RPMLOG_ERR, + } + } + pub fn rpm_exec_transaction( + &self, + solved_pkg_info: &SolvedPkgInfo, + alter_type: &AlterType, + alter_args: &AlterOption, + ) -> Result<()> { + unsafe { + let p = self.parse_rpm_verbosity(); + let pri = (1 << ((p & RPMLOG_PRIMASK) + 1)) - 1; + rpmlogSetMask(pri) + }; + let mut prob_filter_flags = rpmprobFilterFlags_e_RPMPROB_FILTER_OLDPACKAGE; + if alter_type.is_reinstall() { + prob_filter_flags = prob_filter_flags | rpmprobFilterFlags_e_RPMPROB_FILTER_REPLACEPKG; + } + let ts = unsafe { rpmtsCreate() }; + if ts.is_null() { + bail!(ERROR_RDNF_RPMTS_CREATE_FAILED); + }; + let mut trans_flags = rpmtransFlags_e_RPMTRANS_FLAG_NONE; + if alter_args.tsflags_noscripts { + trans_flags |= rpmtransFlags_e_RPMTRANS_FLAG_NOSCRIPTS; + } + let root_ptr = + CString::new(self.rc.cli.installroot.clone()).unwrap_or(CString::new("/").unwrap()); + unsafe { rpmtsSetRootDir(ts, root_ptr.as_ptr()) }; + let mut rpm_ts = RpmTs { + cached_rpms: Vec::new(), + trans_flags, + prob_filter_flags, + ts, + }; + let (_, width) = self.rc.term.size(); + set_callbackfunction(rpm_ts.ts, alter_args.quiet, width); + self.populate_transaction(&mut rpm_ts, solved_pkg_info, alter_args)?; + self.run_transaction(&mut rpm_ts, &self.rc.term, alter_args)?; + Ok(()) + } + pub fn populate_transaction( + &self, + rpm_ts: &mut RpmTs, + solved_pkg_info: &SolvedPkgInfo, + alter_args: &AlterOption, + ) -> Result<()> { + if solved_pkg_info.base.to_install.is_some() { + let pkg_infos = solved_pkg_info.base.to_install.as_ref().unwrap(); + self.trans_add_install_pkgs(rpm_ts, pkg_infos, 0, alter_args)?; + } + if solved_pkg_info.base.to_reinstall.is_some() { + let pkg_infos = solved_pkg_info.base.to_reinstall.as_ref().unwrap(); + self.trans_add_install_pkgs(rpm_ts, pkg_infos, 0, alter_args)?; + } + if solved_pkg_info.base.to_upgrade.is_some() { + let pkg_infos = solved_pkg_info.base.to_upgrade.as_ref().unwrap(); + self.trans_add_install_pkgs(rpm_ts, pkg_infos, 1, alter_args)?; + } + if solved_pkg_info.base.to_remove.is_some() { + let pkg_infos = solved_pkg_info.base.to_remove.as_ref().unwrap(); + self.trans_add_erase_pkg(rpm_ts, pkg_infos); + } + if solved_pkg_info.base.obsoleted.is_some() { + let pkg_infos = solved_pkg_info.base.obsoleted.as_ref().unwrap(); + self.trans_add_erase_pkg(rpm_ts, pkg_infos); + } + if solved_pkg_info.base.to_downgrade.is_some() { + let pkg_infos = solved_pkg_info.base.to_downgrade.as_ref().unwrap(); + self.trans_add_install_pkgs(rpm_ts, pkg_infos, 0, alter_args)?; + if solved_pkg_info.base.removed_by_downgrade.is_some() { + let pkg_infos = solved_pkg_info.base.removed_by_downgrade.as_ref().unwrap(); + self.trans_add_erase_pkg(rpm_ts, pkg_infos); + } + } + Ok(()) + } + pub fn trans_add_install_pkgs( + &self, + rpm_ts: &mut RpmTs, + pkg_infos: &Vec, + upgrade: i32, + alter_args: &AlterOption, + ) -> Result<()> { + for pkg_info in pkg_infos { + let mut location = pkg_info.details.location.clone().unwrap(); + let pkg_name = pkg_info.base.name.as_str(); + let repo_name = pkg_info.base.repo_name.as_str(); + let repo = match self.repos.iter().find(|x| x.psz_id == repo_name) { + Some(repo) => repo, + None => { + bail!(ERROR_RDNF_REPO_NOT_FOUND) + } + }; + if !Path::new(location.as_str()).exists() { + location = match repo.details.base_url.as_ref() { + Some(base_url) => { + let url = + base_url.trim_end_matches("/").to_string() + "/" + location.as_str(); + self.download_pkg_to_cache( + url.as_str(), + pkg_name, + repo, + RPM_CACHE_DIR_NAME, + )? + } + None => { + bail!(ERROR_RDNF_REPO_NOT_FOUND) + } + }; + }; + let (header, gpg_check) = + self.gpgcheck_pkg(rpm_ts, location.as_str(), repo, alter_args)?; + if !gpg_check { + unsafe { + rpmtsSetVSFlags( + rpm_ts.ts, + rpmtsVSFlags(rpm_ts.ts) | RPMVSF_MASK_NODIGESTS | RPMVSF_MASK_NOSIGNATURES, + ); + rpmtsSetVfyLevel(rpm_ts.ts, !RPMSIG_VERIFIABLE_TYPE as i32); + } + } + unsafe { + let file_ptr = CString::new(location.as_str()).unwrap().into_raw(); + rpmtsAddInstallElement( + rpm_ts.ts, + header, + file_ptr as fnpyKey, + upgrade, + 0 as *mut rpmRelocation, + ) + }; + rpm_ts.cached_rpms.push(location); + } + Ok(()) + } + pub fn trans_add_erase_pkg(&self, rpm_ts: &mut RpmTs, pkg_infos: &Vec) { + for pkg_info in pkg_infos { + let pkg_name = CString::new(pkg_info.base.name.as_str()).unwrap(); + let iter = unsafe { + rpmtsInitIterator( + rpm_ts.ts, + rpmDbiTag_e_RPMDBI_LABEL as rpmTag, + pkg_name.as_ptr() as *const c_void, + 0, + ) + }; + loop { + let rpm_header = unsafe { rpmdbNextIterator(iter) }; + if rpm_header.is_null() { + break; + } + let offset = unsafe { rpmdbGetIteratorOffset(iter) }; + if offset > 0 { + unsafe { rpmtsAddEraseElement(rpm_ts.ts, rpm_header, offset as i32) }; + } + } + if !iter.is_null() { + unsafe { rpmdbFreeIterator(iter) }; + } + } + } + pub fn run_transaction( + &self, + rpm_ts: &mut RpmTs, + term: &Term, + alter_args: &AlterOption, + ) -> Result<()> { + let mut rpm_vfy_level_mask = 0; + unsafe { rpmtsOrder(rpm_ts.ts) }; + rpm_ts.do_check(term)?; + unsafe { rpmtsClean(rpm_ts.ts) }; + if alter_args.no_gpg_check { + unsafe { + rpmtsSetVSFlags( + rpm_ts.ts, + rpmtsVSFlags(rpm_ts.ts) | RPMVSF_MASK_NODIGESTS | RPMVSF_MASK_NOSIGNATURES, + ); + rpmtsSetVSFlags(rpm_ts.ts, !RPMSIG_VERIFIABLE_TYPE); + } + } else if alter_args.skip_signatures || alter_args.skip_digest { + if alter_args.skip_signatures { + unsafe { + rpmtsSetVSFlags( + rpm_ts.ts, + rpmtsVSFlags(rpm_ts.ts) | RPMVSF_MASK_NOSIGNATURES, + ); + rpm_vfy_level_mask |= RPMSIG_SIGNATURE_TYPE; + } + } + if alter_args.skip_digest { + unsafe { + rpmtsSetVSFlags(rpm_ts.ts, rpmtsVSFlags(rpm_ts.ts) | RPMVSF_MASK_NODIGESTS); + rpm_vfy_level_mask |= RPMSIG_DIGEST_TYPE; + } + } + unsafe { + rpmtsSetVfyLevel(rpm_ts.ts, !rpm_vfy_level_mask as i32); + } + } + let rc = unsafe { + rpmtsSetFlags(rpm_ts.ts, rpmtransFlags_e_RPMTRANS_FLAG_TEST as u32); + rpmtsRun(rpm_ts.ts, 0 as rpmps, rpm_ts.prob_filter_flags) + }; + if rc != 0 { + println!("a"); + bail!(ERROR_RDNF_TRANSACTION_FAILED); + } + term.write_line("Running transaction")?; + unsafe { rpmtsSetFlags(rpm_ts.ts, rpm_ts.trans_flags as u32) }; + let rc = unsafe { rpmtsRun(rpm_ts.ts, 0 as rpmps, rpm_ts.prob_filter_flags) }; + if rc != 0 { + println!("b"); + bail!(ERROR_RDNF_TRANSACTION_FAILED); + } + Ok(()) + } +} +// pub fn rdnf_rpm_cb( +// ) -> unsafe extern "C" fn(*const c_void, u32, u64, u64, *const c_void, *mut c_void) -> *mut c_void { +// unsafe extern "C" fn cb( +// arg: *const c_void, +// what: u32, +// amount: u64, +// total: u64, +// key: *const c_void, +// data: *mut c_void, +// ) -> *mut c_void { +// let pkg_header = arg as Header; +// let mut callback_data = Box::from_raw(data as *mut RpmTsCallback); +// let file_name_ptr = key as *const c_char; +// let nevra = CStr::from_ptr(headerGetAsString(pkg_header, rpmTag_e_RPMTAG_NEVRA)) +// .to_str() +// .unwrap(); +// match what { +// rpmCallbackType_e_RPMCALLBACK_INST_OPEN_FILE => { +// if file_name_ptr.is_null() { +// println!("rpmcallback_inst_open_file null "); +// return 0 as *mut c_void; +// } else { +// println!( +// "rpmcallback_inst_open_file {}", +// CStr::from_ptr(file_name_ptr).to_str().unwrap() +// ); +// let mode = CString::new("r.ufdio").unwrap(); +// let fd = Fopen(file_name_ptr, mode.as_ptr()); +// callback_data.fs = Some(fd); +// } +// } +// rpmCallbackType_e_RPMCALLBACK_INST_CLOSE_FILE => { +// if callback_data.fs.is_some() { +// let fs_ptr = callback_data.fs.unwrap(); +// if !fs_ptr.is_null() { +// println!( +// "rpmcallback_inst_close_file {}", +// CStr::from_ptr(file_name_ptr).to_str().unwrap() +// ); +// Fclose(fs_ptr); +// callback_data.fs = None; +// } +// } +// } +// rpmCallbackType_e_RPMCALLBACK_INST_START => { +// if !callback_data.quiet { +// println!("{:<20}{}", ACTION_ALTER_INTALL, nevra); +// } +// } +// rpmCallbackType_e_RPMCALLBACK_UNINST_START => { +// if !callback_data.quiet { +// println!("{:<20}{}", ACTION_ALTER_ERASE, nevra); +// } +// } +// rpmCallbackType_e_RPMCALLBACK_SCRIPT_ERROR => { +// let script = match amount as i32 { +// rpmTag_e_RPMTAG_PREIN => "%prein", +// rpmTag_e_RPMTAG_POSTIN => "%postin", +// rpmTag_e_RPMTAG_PREUN => "%preun", +// rpmTag_e_RPMTAG_POSTUN => "%postun", +// _ => "(unkown)", +// }; +// let flag = if total == rpmRC_e_RPMRC_OK as u64 { +// "warning" +// } else { +// "error" +// }; +// println!("package {}: script {} in {}", nevra, flag, script); +// } +// _ => {} +// } + +// let _ = Box::into_raw(callback_data); +// 0 as *mut c_void +// } +// cb +// } +impl RpmTs { + pub fn do_check(&self, term: &Term) -> Result<()> { + let _nresult = unsafe { rpmtsCheck(self.ts) }; + let ps = unsafe { rpmtsProblems(self.ts) }; + if !ps.is_null() { + let n_probs = unsafe { rpmpsNumProblems(ps) }; + if n_probs > 0 { + term.write_line(format!("Found {} problems", n_probs).as_str())?; + let psi = unsafe { rpmpsInitIterator(ps) }; + while unsafe { rpmpsNextIterator(psi) } >= 0 { + let prob = unsafe { rpmpsGetProblem(psi) }; + let msg_ptr = unsafe { rpmProblemString(prob) }; + let msg = unsafe { CStr::from_ptr(msg_ptr).to_str()? }; + if msg.matches("no digest").collect::>().len() >= 1 { + let info = + format!("{}. Use {} to ignore", msg, style("--skipdigest").red()); + term.write_line(info.as_str())?; + } else { + term.write_line(msg)?; + if unsafe { rpmProblemGetType(prob) } == rpmProblemType_e_RPMPROB_REQUIRES { + let error_str = + unsafe { CStr::from_ptr(rpmProblemGetStr(prob)).to_str()? }; + // Error str has the format: + let token = error_str.split(' ').collect::>(); + if token.len() != 3 { + term.write_line("RPM problem string format unsupported")?; + bail!(ERROR_RDNF_INVALID_PARAMETER); + } + let pkg_name = token[0]; + let pkg_symbol = token[1]; + let pkg_version = token[2]; + let pkg_version_c = CString::new(pkg_version).unwrap(); + let pi = unsafe { rpmtsiInit(self.ts) }; + loop { + let pte = unsafe { rpmtsiNext(pi, 0) }; + if pte.is_null() { + break; + } + let cached_pkg_name = + unsafe { CStr::from_ptr(rpmteN(pte)).to_str()? }; + let cached_pkg_evr_ptr = unsafe { rpmteEVR(pte) }; + if cached_pkg_name == token[0] { + let more = pkg_symbol.find(">").is_some() + && unsafe { + rpmvercmp(cached_pkg_evr_ptr, pkg_version_c.as_ptr()) + > 0 + }; + let less = pkg_symbol.find("<").is_some() + && unsafe { + rpmvercmp(cached_pkg_evr_ptr, pkg_version_c.as_ptr()) + < 0 + }; + let equal = pkg_symbol.find("=").is_some() + && unsafe { + rpmvercmp(cached_pkg_evr_ptr, pkg_version_c.as_ptr()) + == 0 + }; + if more || less || equal { + let item=format!("Detected rpm pre-transaction dependency errors. Install {} {} {} first to resolve this failure", + pkg_name,pkg_symbol,pkg_version); + term.write_line(item.as_str())?; + break; + } + } + } + } + } + unsafe { rpmProblemFree(prob) }; + } + unsafe { rpmpsFreeIterator(psi) }; + bail!(ERROR_RDNF_RPM_CHECK); + } + } + Ok(()) + } +} diff --git a/rdnf/src/solv/mod.rs b/rdnf/src/solv/mod.rs new file mode 100644 index 00000000..0ee146e5 --- /dev/null +++ b/rdnf/src/solv/mod.rs @@ -0,0 +1,25 @@ +use solv_sys::ffi::{Queue, Repo }; + + + +pub mod rdnf_pkg; +pub mod rdnf_query; +pub mod rdnf_repo; +pub mod sack; +#[derive(Debug, Clone)] +pub struct SolvRepoInfoInernal { + pub repo: *mut Repo, + pub cookie: Option<[u8; 32]>, + pub n_cookie_set: Option, + pub repo_cache_dir: Option, +} +pub struct SolvPackageList { + pub pkgs: Queue, +} +impl SolvPackageList { + pub fn get_size(&self) -> u32 { + self.pkgs.count as u32 + } +} + + diff --git a/rdnf/src/solv/rdnf_pkg.rs b/rdnf/src/solv/rdnf_pkg.rs new file mode 100644 index 00000000..43687026 --- /dev/null +++ b/rdnf/src/solv/rdnf_pkg.rs @@ -0,0 +1,441 @@ +use std::{ + ffi::{CStr, CString}, + mem, +}; + +use crate::{ + c_lib::{ + create_dataiterator_empty, get_queue_element_value, map_set, map_setall, + pool_disabled_solvable, pool_id2solvable, + }, + errors::{ + ERROR_RDNF_INVALID_PARAMETER, ERROR_RDNF_NO_DATA, ERROR_RDNF_NO_MATCH, + ERROR_RDNF_SOLV_FAILED, + }, + sub_command::{ + info::{PkgInfoBase, PkgInfoDetails, PkgInfoOther}, + install::is_glob, + }, + utils::{c_str_ptr_to_rust_string, format_size}, +}; + +use super::{ + rdnf_query::{init_queue, SolvQuery}, + sack::Solvsack, + SolvPackageList, +}; + +use anyhow::{bail, Result}; +use libc::strtol; + +use solv_sys::ffi::{ + dataiterator_init, dataiterator_step, map_grow, map_init, map_subtract, pool_evrcmp_str, + pool_id2str, pool_solvable2str, queue_insertn, solv_knownid_SOLVABLE_ARCH, + solv_knownid_SOLVABLE_EVR, solv_knownid_SOLVABLE_INSTALLSIZE, solv_knownid_SOLVABLE_NAME, + solv_knownid_SOLVABLE_SUMMARY, solv_knownid_SOLVABLE_URL, solvable_get_location, + solvable_lookup_num, solvable_lookup_str, solver_findproblemrule, solver_problem_count, + solver_problemruleinfo2str, solver_ruleinfo, Dataiterator, Map, Pool, Queue, Repo, Solver, + SolverRuleinfo_SOLVER_RULE_PKG_CONFLICTS, SolverRuleinfo_SOLVER_RULE_PKG_IMPLICIT_OBSOLETES, + SolverRuleinfo_SOLVER_RULE_PKG_INSTALLED_OBSOLETES, + SolverRuleinfo_SOLVER_RULE_PKG_NOT_INSTALLABLE, SolverRuleinfo_SOLVER_RULE_PKG_OBSOLETES, + SolverRuleinfo_SOLVER_RULE_PKG_REQUIRES, SolverRuleinfo_SOLVER_RULE_PKG_SELF_CONFLICT, + EVRCMP_COMPARE, SEARCH_GLOB, SEARCH_STRING, solv_knownid_SOLVABLE_LICENSE, solv_knownid_SOLVABLE_DESCRIPTION, +}; +#[derive(Debug, Clone, Copy)] +pub struct SkipProblem { + pub none: bool, + pub conflicts: bool, + pub obsoletes: bool, + pub disabled: bool, +} +pub fn skip_based_on_type( + solv: *mut Solver, + rule_type: u32, + source: i32, + skip_problem: SkipProblem, +) -> bool { + let mut result = false; + if skip_problem.conflicts { + result = result + || rule_type == SolverRuleinfo_SOLVER_RULE_PKG_CONFLICTS + || rule_type == SolverRuleinfo_SOLVER_RULE_PKG_SELF_CONFLICT; + } + if skip_problem.obsoletes { + result = result + || rule_type == SolverRuleinfo_SOLVER_RULE_PKG_OBSOLETES + || rule_type == SolverRuleinfo_SOLVER_RULE_PKG_IMPLICIT_OBSOLETES + || rule_type == SolverRuleinfo_SOLVER_RULE_PKG_INSTALLED_OBSOLETES; + } + if skip_problem.disabled { + if rule_type == SolverRuleinfo_SOLVER_RULE_PKG_NOT_INSTALLABLE { + let s = unsafe { pool_id2solvable((*solv).pool, source) }; + if unsafe { pool_disabled_solvable((*solv).pool, s) } { + result = true; + }; + } + } + result +} + +impl Solvsack { + pub fn solv_count_pkg_by_name(&self, pkg: &str) -> Result { + let mut p_query = SolvQuery::default(self.clone()); + p_query.solv_apply_single_pkg_filter(pkg)?; + p_query.solv_apply_list_query()?; + let pkg_list = p_query.solv_get_query_result()?; + Ok(pkg_list.get_size()) + } + pub fn solv_find_all_installed(&self) -> Result { + let mut p_query = SolvQuery::default(self.clone()); + p_query.solv_add_system_repo_filter()?; + p_query.solv_apply_list_query()?; + let pkgs = p_query.solv_get_query_result()?; + Ok(pkgs) + } + pub fn solv_find_installed_pkg_by_multiple_names( + &self, + pkg_names: Vec, + ) -> Result { + let mut p_query = SolvQuery::default(self.clone()); + p_query.solv_add_system_repo_filter()?; + p_query.package_names = Some(pkg_names.clone()); + p_query.solv_apply_list_query()?; + let pkgs = p_query.solv_get_query_result()?; + Ok(pkgs) + } + pub fn solv_find_installed_pkg_by_name(&self, pkg_name: &str) -> Result { + let mut p_query = SolvQuery::default(self.clone()); + p_query.solv_add_system_repo_filter()?; + p_query.solv_apply_single_pkg_filter(pkg_name)?; + p_query.solv_apply_list_query()?; + let pkgs = p_query.solv_get_query_result()?; + Ok(pkgs) + } + pub fn solv_get_pkg_name_by_id(&self, id: i32) -> Result { + let p_solv = pool_id2solvable(self.pool, id); + if p_solv.is_null() { + bail!(ERROR_RDNF_NO_DATA); + } + unsafe { + let psz_temp = pool_id2str(self.pool, (*p_solv).name); + if psz_temp.is_null() { + bail!(ERROR_RDNF_NO_DATA) + } + Ok(CStr::from_ptr(psz_temp).to_str().unwrap().to_string()) + } + } + pub fn solv_get_pkg_nevr_by_id(&self, id: i32) -> Result { + let p_solv = pool_id2solvable(self.pool, id); + if p_solv.is_null() { + bail!(ERROR_RDNF_NO_DATA); + } + unsafe { + let psz_temp = pool_solvable2str(self.pool, p_solv); + if psz_temp.is_null() { + bail!(ERROR_RDNF_NO_DATA) + } + Ok(CStr::from_ptr(psz_temp).to_str().unwrap().to_string()) + } + } + pub fn solv_find_available_pkg_by_name(&self, pkg_name: &str) -> Result { + let mut p_query = SolvQuery::default(self.clone()); + p_query.solv_add_available_repo_filter()?; + p_query.solv_apply_single_pkg_filter(pkg_name)?; + p_query.solv_apply_list_query()?; + Ok(p_query.solv_get_query_result()?) + } + pub fn solv_find_highest_available(&self, pkg_name: &str) -> Result { + let pkg_list = self.solv_find_available_pkg_by_name(pkg_name)?; + let mut highest_available = pkg_list.get_pkg_id(0); + let count = pkg_list.get_size(); + for index in 1..count { + let id = pkg_list.get_pkg_id(index); + if self.solv_cmp_evr(id, highest_available)? > 0 { + highest_available = id; + }; + } + Ok(highest_available) + } + pub fn solv_find_highest_or_lowest_installed( + &self, + pkg_name: &str, + is_higher: bool, + ) -> Result { + let installed_pkg_list = self.solv_find_installed_pkg_by_name(pkg_name)?; + let mut high_or_low = installed_pkg_list.get_pkg_id(0); + if high_or_low != 0 { + let count = installed_pkg_list.get_size(); + for index in 1..count { + let id = installed_pkg_list.get_pkg_id(index); + let cmp = self.solv_cmp_evr(id, high_or_low)?; + match is_higher { + true => { + if cmp > 0 { + high_or_low = id; + } + } + false => { + if cmp < 0 { + high_or_low = id; + } + } + } + } + } + Ok(high_or_low) + } + pub fn solv_find_highest_installed(&self, pkg_name: &str) -> Result { + Ok(self.solv_find_highest_or_lowest_installed(pkg_name, true)?) + } + pub fn solv_find_lowest_installed(&self, pkg_name: &str) -> Result { + Ok(self.solv_find_highest_or_lowest_installed(pkg_name, false)?) + } + pub fn solv_cmp_evr(&self, id1: i32, id2: i32) -> Result { + let pool = self.pool; + let solv1 = pool_id2solvable(pool, id1); + let solv2 = pool_id2solvable(pool, id2); + if solv1.is_null() || solv2.is_null() { + bail!(ERROR_RDNF_INVALID_PARAMETER); + } + unsafe { + let evr1 = solvable_lookup_str(solv1, solv_knownid_SOLVABLE_EVR as i32); + let evr2 = solvable_lookup_str(solv2, solv_knownid_SOLVABLE_EVR as i32); + let p = pool_evrcmp_str(pool, evr1, evr2, EVRCMP_COMPARE as i32); + Ok(p) + } + } + pub fn solv_report_problems(&self, solv: *mut Solver, skip_problem: SkipProblem) -> Result<()> { + let mut count = unsafe { solver_problem_count(solv) }; + let mut source = 0; + let mut target = 0; + let mut dep = 0; + let mut prv_pkg_name = ""; + let mut error = ""; + let mut total_problems = 0; + while count > 0 { + let problem_id = unsafe { solver_findproblemrule(solv, count as i32) }; + let rule_type = + unsafe { solver_ruleinfo(solv, problem_id, &mut source, &mut target, &mut dep) }; + if skip_based_on_type(solv, rule_type, source, skip_problem) { + count -= 1; + continue; + }; + let psz_problem = + unsafe { solver_problemruleinfo2str(solv, rule_type, source, target, dep) }; + let problem = unsafe { CStr::from_ptr(psz_problem).to_str().unwrap() }; + if !skip_problem.none && rule_type == SolverRuleinfo_SOLVER_RULE_PKG_REQUIRES { + let (_, beg) = problem.split_once("requires").unwrap(); + let (beg, _) = beg.split_once(",").unwrap(); + let pkg_name = beg.trim_start().trim_end(); + if pkg_name == prv_pkg_name { + continue; + } + prv_pkg_name = pkg_name; + match self.solv_find_available_pkg_by_name(pkg_name) { + Ok(_) => { + continue; + } + Err(_) => {} + }; + } + error = ERROR_RDNF_SOLV_FAILED; + total_problems += 1; + println!("{}. {}", total_problems, problem); + } + if error != "" { + bail!("Found {} problem(s) while resolving", total_problems); + } + Ok(()) + } + pub fn solv_get_pkginfo_base_by_id(&self, pkg_id: i32) -> Result { + let solv = pool_id2solvable(self.pool, pkg_id); + if solv.is_null() { + bail!(ERROR_RDNF_NO_DATA); + } + let name = c_str_ptr_to_rust_string(unsafe { + solvable_lookup_str(solv, solv_knownid_SOLVABLE_NAME as i32) + }) + .unwrap_or("".to_string()); + let arch = c_str_ptr_to_rust_string(unsafe { + solvable_lookup_str(solv, solv_knownid_SOLVABLE_ARCH as i32) + }) + .unwrap_or("".to_string()); + let evr = c_str_ptr_to_rust_string(unsafe { + solvable_lookup_str(solv, solv_knownid_SOLVABLE_EVR as i32) + }) + .unwrap_or("".to_string()); + let (epoch, version, release) = solv_split_evr(evr.as_str()); + let mut dw_epoch = 0; + if epoch != "" { + unsafe { + let epoch_c = CString::new(epoch).unwrap(); + dw_epoch = strtol(epoch_c.as_ptr(), 0 as *mut *mut i8, 10); + } + }; + let repo_name_ptr = unsafe { (*(*solv).repo).name }; + if repo_name_ptr.is_null() { + bail!(ERROR_RDNF_NO_DATA); + }; + let repo_name = unsafe { CStr::from_ptr(repo_name_ptr).to_str()? }.to_string(); + Ok(PkgInfoBase { + epoch: dw_epoch as u32, + name: name.to_string(), + version: version.to_string(), + release: release.to_string(), + arch, + evr, + repo_name, + }) + } + pub fn solv_get_pkginfo_details_by_id(&self, pkg_id: i32) -> Result { + let solv = pool_id2solvable(self.pool, pkg_id); + if solv.is_null() { + bail!(ERROR_RDNF_NO_DATA); + }; + let summary = c_str_ptr_to_rust_string(unsafe { + solvable_lookup_str(solv, solv_knownid_SOLVABLE_SUMMARY as i32) + }) + .unwrap_or("".to_string()); + let location = + c_str_ptr_to_rust_string(unsafe { solvable_get_location(solv, 0 as *mut u32) }); + let install_size = + unsafe { solvable_lookup_num(solv, solv_knownid_SOLVABLE_INSTALLSIZE as i32, 0) }; + let formatted_size = format_size(install_size); + Ok(PkgInfoDetails { + install_size, + formatted_size, + summary, + location, + }) + } + pub fn solv_get_pkginfo_other_by_id(&self, pkg_id: i32) -> Result { + let solv = pool_id2solvable(self.pool, pkg_id); + let url = c_str_ptr_to_rust_string(unsafe { + solvable_lookup_str(solv, solv_knownid_SOLVABLE_URL as i32) + }) + .unwrap_or("None".to_string()); + let license=c_str_ptr_to_rust_string(unsafe { + solvable_lookup_str(solv, solv_knownid_SOLVABLE_LICENSE as i32) + }).unwrap_or("None".to_string()); + let description=c_str_ptr_to_rust_string(unsafe { + solvable_lookup_str(solv, solv_knownid_SOLVABLE_DESCRIPTION as i32) + }).unwrap_or("None".to_string()); + Ok(PkgInfoOther { + url, + license, + description, + }) + } + pub fn solv_get_pkginfo_by_id(&self, pkg_id: i32, which_info: i32) -> Result<&str> { + let solv = pool_id2solvable(self.pool, pkg_id); + Ok(unsafe { CStr::from_ptr(solvable_lookup_str(solv, which_info)).to_str()? }) + } + pub fn solv_get_pkg_location_by_id(&self, pkg_id: i32) -> Result<&str> { + let solv = pool_id2solvable(self.pool, pkg_id); + Ok(unsafe { CStr::from_ptr(solvable_get_location(solv, 0 as *mut u32)).to_str()? }) + } + pub fn solv_get_pkg_install_size_by_id(&self, pkg_id: i32) -> Result { + let solv = pool_id2solvable(self.pool, pkg_id); + Ok(unsafe { solvable_lookup_num(solv, solv_knownid_SOLVABLE_INSTALLSIZE as i32, 0) }) + } +} +impl SolvQuery { + pub fn solv_get_query_result(&self) -> Result { + if self.queue_result.count == 0 { + bail!(ERROR_RDNF_NO_MATCH); + } + let mut solv_pkgs_list = SolvPackageList { pkgs: init_queue() }; + unsafe { + queue_insertn( + &mut solv_pkgs_list.pkgs as *mut Queue, + solv_pkgs_list.pkgs.count, + self.queue_result.count, + self.queue_result.elements, + ); + } + Ok(solv_pkgs_list) + } +} +impl SolvPackageList { + pub fn get_pkg_id(&self, index: u32) -> i32 { + let p = &self.pkgs as *const Queue; + get_queue_element_value(p, index) + } + pub fn queue_to_pkg_list(queue: &mut Queue) -> Result { + if queue.count == 0 { + bail!(ERROR_RDNF_NO_MATCH) + }; + let mut solv_pkgs_list = SolvPackageList { pkgs: init_queue() }; + unsafe { + queue_insertn( + &mut solv_pkgs_list.pkgs, + solv_pkgs_list.pkgs.count, + queue.count, + queue.elements, + ); + }; + Ok(solv_pkgs_list) + } +} +pub fn solv_add_excludes(pool: *mut Pool, excludes: &Vec) { + let mut excludes_map = unsafe { init_map((*pool).nsolvables) }; + solv_data_iterator(pool, excludes, &mut excludes_map); + unsafe { + if (*pool).considered.is_null() { + (*pool).considered = libc::malloc(mem::size_of::()) as *mut Map; + map_init((*pool).considered, (*pool).nsolvables); + } else { + map_grow((*pool).considered, (*pool).nsolvables); + } + map_setall((*pool).considered); + map_subtract((*pool).considered, &excludes_map as *const Map); + } +} +pub fn solv_data_iterator(pool: *mut Pool, excludes: &Vec, map: &mut Map) { + let mut di = create_dataiterator_empty(); + let di_ptr = &mut di as *mut Dataiterator; + let keyname = solv_knownid_SOLVABLE_NAME; + for ele in excludes { + let mut flags = SEARCH_STRING; + if is_glob(ele.as_str()) { + flags = SEARCH_GLOB; + }; + + unsafe { + let temp = CString::new(ele.as_str()).unwrap(); + dataiterator_init( + di_ptr, + pool, + 0 as *mut Repo, + 0, + keyname as i32, + temp.as_ptr(), + flags as i32, + ); + while dataiterator_step(di_ptr) != 0 { + map_set(map as *mut Map, di.solvid); + } + } + } +} +pub fn init_map(n: i32) -> Map { + let mut map = Map { + map: CString::new("").unwrap().into_raw() as *mut u8, + size: 0, + }; + unsafe { + map_init(&mut map as *mut Map, n); + }; + map +} +pub fn solv_split_evr(evr: &str) -> (&str, &str, &str) { + let (evr, rest) = match evr.split_once(":") { + Some(s) => s, + None => ("", evr), + }; + let (version, release) = match rest.split_once("-") { + Some(s) => s, + None => ("", rest), + }; + (evr, version, release) +} diff --git a/rdnf/src/solv/rdnf_query.rs b/rdnf/src/solv/rdnf_query.rs new file mode 100644 index 00000000..e5127fa1 --- /dev/null +++ b/rdnf/src/solv/rdnf_query.rs @@ -0,0 +1,322 @@ +use std::ffi::{CStr, CString}; + +use solv_sys::ffi::{ + queue_init, queue_insertn, selection_filter, selection_make, Queue, Solver, SELECTION_CANON, + SELECTION_DOTARCH, SELECTION_FILELIST, SELECTION_GLOB, SELECTION_NAME, SELECTION_NOCASE, + SELECTION_PROVIDES, SELECTION_REL, SOLVER_DISTUPGRADE, SOLVER_SELECTMASK, + SOLVER_SETREPO, SOLVER_SETVENDOR, SOLVER_SOLVABLE, SOLVER_SOLVABLE_ALL, SOLVER_SOLVABLE_NAME, + SOLVER_SOLVABLE_ONE_OF, SOLVER_SOLVABLE_REPO, +}; + +use crate::{ + c_lib::{ + get_pool_solvables_value, get_pool_whatprovidesdata_value, get_queue_element_value, + is_pseudo_package, pool_id2repo, pool_id2solvable, pool_match_nevr, pool_whatprovides, + queue_empty, queue_push, queue_push2, + }, + default::SYSTEM_REPO_NAME, + errors::ERROR_RDNF_INVALID_PARAMETER, +}; + +use super::{sack::Solvsack, SolvPackageList}; +use anyhow::{bail, Result}; +pub struct SolvQuery { + pub sack: Solvsack, + pub queue_job: Queue, + pub p_solv: Option<*mut Solver>, + pub queue_repo_filter: Queue, + pub package_names: Option>, + pub queue_result: Queue, + pub dw_new_packages: Option, + // pub scope: Option, +} +impl SolvQuery { + pub fn default(solv_sack: Solvsack) -> Self { + SolvQuery { + sack: solv_sack, + queue_job: init_queue(), + p_solv: None, + queue_repo_filter: init_queue(), + package_names: None, + queue_result: init_queue(), + dw_new_packages: None, + // scope: None, + } + } + // pub fn solv_apply_pkg_filter(&mut self,pkg_names:&Vec)->Result<()>{ + // let p=pkg_names.clone(); + // self.package_names=Some(p); + // Ok(()) + // } + pub fn solv_apply_list_query(&mut self) -> Result<()> { + let mut queue_temp = init_queue(); + let queue_temp_ptr = &mut queue_temp as *mut Queue; + let flags = SELECTION_NAME | SELECTION_PROVIDES | SELECTION_GLOB; + let flags = flags | (SELECTION_CANON | SELECTION_DOTARCH | SELECTION_REL); + self.solv_generate_common_job(flags)?; + // let scope = self.scope.clone(); + // if scope.as_ref().is_some() && scope.as_ref().unwrap().is_upgrades() { + // self.solv_apply_up_down_scope(true); + // todo!(); + // } else if scope.as_ref().is_some() && scope.as_ref().unwrap().is_down_grades() { + // } else + if self.queue_job.count > 0 { + let mut index: u32 = 0; + while index < self.queue_job.count as u32 { + queue_empty(queue_temp_ptr); + let what = + get_queue_element_value(&mut self.queue_job as *mut Queue, index + 1 as u32); + let how = SOLVER_SELECTMASK + & get_queue_element_value(&mut self.queue_job as *mut Queue, index) as u32; + let pool = self.sack.pool; + if how == SOLVER_SOLVABLE_ALL { + let mut p = 2; + unsafe { + while p < (*pool).nsolvables { + let solvable_item = get_pool_solvables_value(pool, p as u32); + if !(*solvable_item).repo.is_null() + && !is_pseudo_package(pool, solvable_item) + { + queue_push(queue_temp_ptr, p); + }; + p += 1; + } + } + } else if how == SOLVER_SOLVABLE_REPO { + let repo = pool_id2repo(pool, what); + if !repo.is_null() { + unsafe { + let mut p = (*repo).start; + let mut s = pool_id2solvable((*repo).pool, p); + while p < (*repo).end { + let solvable_item = get_pool_solvables_value(pool, p as u32); + if (*s).repo == repo && !is_pseudo_package(pool, solvable_item) { + queue_push(queue_temp_ptr, p); + }; + p += 1; + s = pool_id2solvable((*repo).pool, p); + } + } + } + } else { + let mut pp = if how == SOLVER_SOLVABLE { + 0 + } else { + if how == SOLVER_SOLVABLE_ONE_OF { + what + } else { + pool_whatprovides(pool, what) + } + }; + let mut p = if how == SOLVER_SOLVABLE { + what + } else { + get_pool_whatprovidesdata_value(pool, pp) + }; + pp += 1; + while p != 0 { + let s = pool_id2solvable(pool, p); + let solvable_item = get_pool_solvables_value(pool, p as u32); + if !(how == SOLVER_SOLVABLE_NAME && pool_match_nevr(pool, s, what) == 0) + && !(is_pseudo_package(pool, solvable_item)) + { + queue_push(queue_temp_ptr, p); + } + p = get_pool_whatprovidesdata_value(pool, pp); + pp += 1; + } + } + unsafe { + queue_insertn( + &mut self.queue_result as *mut Queue, + self.queue_result.count, + queue_temp.count, + queue_temp.elements, + ); + } + index += 2; + } + } else if self.package_names.is_none() { + let pool = self.sack.pool; + // let mut p = 2; + unsafe { + // while p < (*pool).nsolvables { + // let solvable_item = get_pool_solvables_value(pool, p as u32); + // if !(*solvable_item).repo.is_null() && !is_pseudo_package(pool, solvable_item) { + // queue_push(queue_temp_ptr, p); + // }; + // p += 1; + // } + for id in 2..(*pool).nsolvables { + let solvable_item = get_pool_solvables_value(pool, id as u32); + if !(*solvable_item).repo.is_null() && !is_pseudo_package(pool, solvable_item) { + queue_push(&mut self.queue_result, id); + }; + } + } + } + Ok(()) + } + // pub fn solv_apply_up_down_scope(&mut self, up: bool) -> Result<()> { + // let p = if self.package_names.is_none() { + // self.sack.solv_find_all_installed()? + // } else { + // let p = self.package_names.clone().unwrap(); + // self.sack.solv_find_installed_pkg_by_multiple_names(p)? + // }; + // Ok(()) + // } + pub fn solv_add_system_repo_filter(&mut self) -> Result<()> { + let pool = self.sack.pool; + unsafe { + queue_push2( + &mut self.queue_repo_filter as *mut Queue, + (SOLVER_SOLVABLE_REPO | SOLVER_SETREPO) as i32, + (*(*pool).installed).repoid, + ); + } + Ok(()) + } + pub fn solv_add_available_repo_filter(&mut self) -> Result<()> { + let pool = self.sack.pool; + unsafe { + for repoid in 1..(*pool).nrepos{ + let repo = pool_id2repo(pool, repoid); + if !repo.is_null() { + let repo_name = CStr::from_ptr((*repo).name).to_str()?; + if SYSTEM_REPO_NAME.to_lowercase() != repo_name.to_lowercase() { + queue_push2( + &mut self.queue_repo_filter as *mut Queue, + (SOLVER_SOLVABLE_REPO | SOLVER_SETREPO | SOLVER_SETVENDOR) as i32, + (*repo).repoid, + ); + } + } + } + } + Ok(()) + } + pub fn solv_apply_single_pkg_filter(&mut self, pkg_name: &str) -> Result<()> { + self.package_names = Some(vec![pkg_name.to_string()]); + Ok(()) + } + pub fn solv_generate_common_job(&mut self, select_flags:u32) -> Result { + let mut queue_job = init_queue(); + let queue_job_ptr = &mut queue_job as *mut Queue; + let pool = self.sack.pool; + match &self.package_names { + Some(pkgs) => { + for pkg in pkgs { + // let mut ret_flags = 0; + let mut flags = select_flags; + unsafe { + queue_empty(queue_job_ptr); + if pool.is_null() + || (*pool).solvables.is_null() + || (*pool).whatprovides.is_null() + { + bail!(ERROR_RDNF_INVALID_PARAMETER); + } + let pkg_ptr = CString::new(pkg.as_str()).unwrap(); + let mut ret_flags = + selection_make(pool, queue_job_ptr, pkg_ptr.as_ptr(), flags as i32); + if self.queue_repo_filter.count != 0 { + selection_filter( + pool, + queue_job_ptr, + &mut self.queue_repo_filter as *mut Queue, + ); + } + if queue_job.count == 0 { + flags = flags | SELECTION_NOCASE; + ret_flags = + selection_make(pool, queue_job_ptr, pkg_ptr.as_ptr(), flags as i32); + if self.queue_repo_filter.count != 0 { + selection_filter( + pool, + queue_job_ptr, + &mut self.queue_repo_filter as *mut Queue, + ); + } + if queue_job.count != 0 { + println!("[ignoring case for {}]", pkg.as_str()); + }() + } + if queue_job.count != 0 { + if (ret_flags & SELECTION_FILELIST as i32) != 0 { + println!("[using file list match for {}]", pkg.as_str()); + } + if (ret_flags & SELECTION_PROVIDES as i32) != 0 { + println!("[using capability match for {}]", pkg.as_str()); + } + queue_insertn( + &mut self.queue_job as *mut Queue, + self.queue_job.count, + queue_job.count, + queue_job.elements, + ); + } + } + } + } + None => { + if self.queue_repo_filter.count != 0 { + queue_empty(queue_job_ptr); + queue_push2(queue_job_ptr, SOLVER_SOLVABLE_ALL as i32, 0); + if self.queue_repo_filter.count != 0 { + unsafe { + selection_filter( + pool, + queue_job_ptr, + &mut self.queue_repo_filter as *mut Queue, + ); + queue_insertn( + &mut self.queue_job as *mut Queue, + self.queue_job.count, + queue_job.count, + queue_job.elements, + ); + } + } + } + } + } + Ok(0) + } +} +pub fn init_queue() -> Queue { + let mut queue = Queue { + elements: &mut 0 as *mut i32, + count: 0, + alloc: &mut 0 as *mut i32, + left: 0, + }; + unsafe { + queue_init(&mut queue as *mut Queue); + }; + queue +} +pub fn _solv_add_dist_upgrade_job(queue_job: &mut Queue) -> Result<()> { + queue_push2( + queue_job as *mut Queue, + (SOLVER_DISTUPGRADE | SOLVER_SOLVABLE_ALL) as i32, + 0, + ); + Ok(()) +} + +impl Solvsack { + pub fn solv_find_all_up_down_candidates( + &mut self, + installed_pkgs: &SolvPackageList, + _up: bool, + _queue_result: &Queue, + ) -> Result<()> { + let _queue_up_down = init_queue(); + let dw_size = installed_pkgs.get_size(); + for index in 0..dw_size { + let _id = installed_pkgs.get_pkg_id(index as u32); + } + Ok(()) + } +} diff --git a/rdnf/src/solv/rdnf_repo.rs b/rdnf/src/solv/rdnf_repo.rs new file mode 100644 index 00000000..aa2569fa --- /dev/null +++ b/rdnf/src/solv/rdnf_repo.rs @@ -0,0 +1,211 @@ +use std::{ + ffi::{c_long, CString}, + fs::{create_dir_all, rename}, + mem::size_of_val, +}; + +use anyhow::{bail, Result}; +use libc::{ + c_void, fchmod, fclose, fdopen, fopen, fread, fseek, fwrite, mkstemp, rewind, FILE, SEEK_END, +}; +use solv_sys::ffi::{ + repo_add_repomdxml, repo_add_rpmmd, repo_add_solv, repo_add_updateinfoxml, repo_write, s_Repo, + solv_xfopen, REPO_EXTEND_SOLVABLES, _IO_FILE, +}; + +use crate::{ + default::{SOLVCACHE_DIR_NAME, SOLV_COOKIE_LEN}, + errors::{ + ERROR_RDNF_ADD_SOLV, ERROR_RDNF_INVALID_PARAMETER, ERROR_RDNF_REPO_WRITE, + ERROR_RDNF_SOLV_IO, + }, + repomd::RepoMd, +}; + +use super::SolvRepoInfoInernal; +pub fn solv_user_metadata_cache( + solv_repo_info: &SolvRepoInfoInernal, + cache_file_path: &str, +) -> Result { + unsafe { + let cache_file_ptr = CString::new(cache_file_path).unwrap(); + let mode = CString::new("r").unwrap(); + let fp = fopen(cache_file_ptr.as_ptr(), mode.as_ptr()); + if fp.is_null() { + return Ok(false); + } + let mut temp_cookie: [u8; SOLV_COOKIE_LEN] = [0; SOLV_COOKIE_LEN]; + let temp_cookie_ptr = temp_cookie.as_mut_ptr(); + let off_set = size_of_val(&temp_cookie); + let off_set_neg = off_set as i32 * -1; + if fseek(fp, off_set_neg as c_long, SEEK_END) != 0 + || fread(temp_cookie_ptr as *mut c_void, off_set, 1, fp) != 1 + { + bail!(ERROR_RDNF_SOLV_IO); + } + let cookie = solv_repo_info.cookie.unwrap(); + if temp_cookie != cookie { + bail!(ERROR_RDNF_SOLV_IO); + } + rewind(fp); + if repo_add_solv(solv_repo_info.repo, fp as *mut _IO_FILE, 0) != 0 { + bail!(ERROR_RDNF_ADD_SOLV); + }; + fclose(fp); + } + Ok(true) +} + +pub fn solv_read_yum_repo( + p_repo: &*mut s_Repo, + repo_md_file: String, + repo_md: RepoMd, +) -> Result<()> { + solv_load_repomd(*p_repo, &repo_md_file)?; + if let Some(primary) = repo_md.primary { + solv_load_repomd_primary(*p_repo, primary.location.as_str())?; + } + if let Some(filelists) = repo_md.filelists { + solv_load_repomd_filelists(*p_repo, filelists.location.as_str())?; + } + if let Some(updateinfo) = repo_md.updateinfo { + solv_load_repomd_updateinfo(*p_repo, updateinfo.location.as_str())?; + } + if let Some(other) = repo_md.other { + solv_load_repomd_other(*p_repo, other.location.as_str())?; + } + Ok(()) +} +pub fn solv_load_repomd(p_repo: *mut s_Repo, repo_md_file: &String) -> Result<()> { + unsafe { + let file_name = CString::new(repo_md_file.as_str()).unwrap(); + let mode = CString::new("r").unwrap(); + let fp = fopen(file_name.as_ptr(), mode.as_ptr()); + if fp.is_null() { + println!("a {}", repo_md_file); + bail!(ERROR_RDNF_SOLV_IO); + } + if repo_add_repomdxml(p_repo, fp as *mut _IO_FILE, 0) != 0 { + println!("b {}", repo_md_file); + bail!(ERROR_RDNF_SOLV_IO); + } + fclose(fp as *mut FILE); + } + Ok(()) +} +pub fn solv_load_repomd_primary(p_repo: *mut s_Repo, primary: &str) -> Result<()> { + unsafe { + let psz_primary = CString::new(primary).unwrap(); + let mode = CString::new("r").unwrap(); + let fp = solv_xfopen(psz_primary.as_ptr(), mode.as_ptr()); + if fp.is_null() { + bail!(ERROR_RDNF_SOLV_IO) + } + if repo_add_rpmmd(p_repo, fp, 0 as *const i8, 0) != 0 { + println!("c"); + bail!(ERROR_RDNF_SOLV_IO) + }; + fclose(fp as *mut FILE); + } + Ok(()) +} +pub fn solv_load_repomd_filelists(p_repo: *mut s_Repo, filelists: &str) -> Result<()> { + let psz_filelists = CString::new(filelists).unwrap(); + let mode = CString::new("r").unwrap(); + let language = CString::new("FL").unwrap(); + unsafe { + let fp = solv_xfopen(psz_filelists.as_ptr(), mode.as_ptr()); + if fp.is_null() { + println!("e {}", filelists); + bail!(ERROR_RDNF_SOLV_IO) + } + if repo_add_rpmmd(p_repo, fp, language.as_ptr(), REPO_EXTEND_SOLVABLES as i32) != 0 { + println!("f {}", filelists); + bail!(ERROR_RDNF_SOLV_IO) + } + fclose(fp as *mut FILE); + } + Ok(()) +} +pub fn solv_load_repomd_updateinfo(p_repo: *mut s_Repo, updateinfo: &str) -> Result<()> { + let psz_updateinfo = CString::new(updateinfo).unwrap(); + let mode = CString::new("r").unwrap(); + unsafe { + let fp = solv_xfopen(psz_updateinfo.as_ptr(), mode.as_ptr()); + if fp.is_null() { + println!("g {}", updateinfo); + bail!(ERROR_RDNF_SOLV_IO) + } + if repo_add_updateinfoxml(p_repo, fp, 0) != 0 { + println!("h {}", updateinfo); + bail!(ERROR_RDNF_SOLV_IO) + } + fclose(fp as *mut FILE); + } + Ok(()) +} +pub fn solv_load_repomd_other(p_repo: *mut s_Repo, other: &str) -> Result<()> { + let psz_other = CString::new(other).unwrap(); + let mode = CString::new("r").unwrap(); + let language = CString::new("en").unwrap(); + unsafe { + let fp = solv_xfopen(psz_other.as_ptr(), mode.as_ptr()); + if fp.is_null() { + println!("i {}", other); + bail!(ERROR_RDNF_SOLV_IO) + } + if repo_add_rpmmd(p_repo, fp, language.as_ptr(), REPO_EXTEND_SOLVABLES as i32) != 0 { + println!("j {}", other); + bail!(ERROR_RDNF_SOLV_IO) + } + fclose(fp as *mut FILE); + } + Ok(()) +} + +pub fn solv_create_metadata_cache( + solv_repo_info: &SolvRepoInfoInernal, + repo_id: &str, +) -> Result<()> { + let solv_cache_dir = solv_repo_info.repo_cache_dir.clone().unwrap() + SOLVCACHE_DIR_NAME; + create_dir_all(solv_cache_dir.clone())?; + let temp_solv_file = solv_cache_dir.clone() + "/" + ".newsolv-XXXXXX"; + let temp_solv_file_ptr = CString::new(temp_solv_file.as_str()).unwrap().into_raw(); + unsafe { + let fd = mkstemp(temp_solv_file_ptr); + if fd < 0 { + println!("k {}", repo_id); + bail!(ERROR_RDNF_SOLV_IO); + } + fchmod(fd, 0o444); + let mode = CString::new("w").unwrap(); + let fp = fdopen(fd, mode.as_ptr()); + if fp.is_null() { + println!("l {}", repo_id); + bail!(ERROR_RDNF_SOLV_IO); + } + let p_repo = solv_repo_info.repo; + if repo_write(p_repo, fp as *mut _IO_FILE) != 0 { + bail!(ERROR_RDNF_REPO_WRITE); + } + let cookie = solv_repo_info.cookie.unwrap(); + if fwrite(cookie.as_ptr() as *const c_void, SOLV_COOKIE_LEN, 1, fp) != 1 { + // println!("m {}", repo_id); + bail!(ERROR_RDNF_SOLV_IO) + } + if (*p_repo).pool.is_null() { + bail!(ERROR_RDNF_INVALID_PARAMETER); + } + // let solvables_start=(*(*p_repo).pool).solvables; + // slice::from_raw_parts(solvables_start, len); + // let temp_solv_file = CStr::from_ptr(temp_solv_file).to_str().unwrap(); + let temp_solv_file = CString::from_raw(temp_solv_file_ptr); + let solv_file_path = solv_cache_dir + "/" + repo_id + ".solv"; + rename(temp_solv_file.to_str().unwrap(), solv_file_path.clone())?; + // let mut perms=fs::metadata(solv_file_path.clone())?.permissions(); + // perms.set_readonly(true); + // fs::set_permissions(solv_file_path.clone(), perms)?; + } + + Ok(()) +} diff --git a/rdnf/src/solv/sack.rs b/rdnf/src/solv/sack.rs new file mode 100644 index 00000000..aedd245a --- /dev/null +++ b/rdnf/src/solv/sack.rs @@ -0,0 +1,129 @@ +use std::{ + ffi::{c_uint, CStr, CString}, + io::Read, + mem::size_of, + os::raw::c_void, +}; + +use anyhow::{bail, Result}; +use libc::{snprintf, fclose}; +use solv_sys::ffi::{ + pool_create, pool_createwhatprovides, pool_set_flag, pool_set_installed, pool_set_rootdir, + pool_setarch, repo_create, s_Pool, solv_chksum_add, solv_chksum_create, solv_chksum_free, + solv_knownid_REPOKEY_TYPE_SHA256, Repo, POOL_FLAG_ADDFILEPROVIDESFILTERED, _IO_FILE, REPO_REUSE_REPODATA, RPM_ADD_WITH_HDRID, REPO_USE_ROOTDIR, repo_add_rpmdb_reffp, +}; + +use crate::{ + conf::ConfigMain, + default::{SOLV_COOKIE_IDENT, SOLV_COOKIE_LEN}, + errors::{ERROR_RDNF_INVALID_PARAMETER, ERROR_RDNF_SOLV_CHKSUM}, + Cli, +}; + +const SYSTEM_REPO_NAME: &str = "@System"; +#[derive(Debug, Clone)] +pub struct Solvsack { + pub pool: *mut s_Pool, + pub dw_num_of_command_pkgs: usize, + pub cachedir: String, + pub rootdir: String, +} +unsafe impl Send for Solvsack {} +impl Solvsack { + pub fn from(conf: &ConfigMain, cli: &Cli,) -> Result { + unsafe { + let pool = pool_create(); + if pool.is_null() { + bail!(ERROR_RDNF_INVALID_PARAMETER); + } + let root = CString::new(cli.installroot.as_str()).unwrap_or(CString::new("/").unwrap()); + pool_set_rootdir(pool, root.as_ptr()); + let evr = CString::new(conf.var_base_arch.as_str()).unwrap(); + pool_setarch(pool, evr.as_ptr()); + pool_set_flag(pool, POOL_FLAG_ADDFILEPROVIDESFILTERED as i32, 1); + let system_repo = CString::new(SYSTEM_REPO_NAME).unwrap(); + let repo: *mut Repo = repo_create(pool, system_repo.as_ptr()); + if !cli.alldeps { + let cache_dir = CString::new(conf.cachedir.as_str()).unwrap(); + let mode = CString::new("r").unwrap(); + let p_cache_file = libc::fopen(cache_dir.as_ptr(), mode.as_ptr()); + let dw_flags=REPO_REUSE_REPODATA | RPM_ADD_WITH_HDRID | REPO_USE_ROOTDIR; + if repo_add_rpmdb_reffp(repo, p_cache_file as *mut _IO_FILE, dw_flags as i32) != 0 { + bail!("Failed to init solvack,can't open rpmdb"); + }; + if !p_cache_file.is_null() { + fclose(p_cache_file); + } + } + if repo.is_null() { + bail!(ERROR_RDNF_INVALID_PARAMETER); + } + pool_set_installed(pool, repo); + pool_createwhatprovides(pool); + Ok(Solvsack { + pool, + dw_num_of_command_pkgs: 0, + cachedir: conf.cachedir.clone(), + rootdir: cli.installroot.clone(), + }) + } + } +} +pub fn solv_create_cache_name(name: &String, url: &String) -> Result { + unsafe { + let p_chk_sum = solv_chksum_create(solv_knownid_REPOKEY_TYPE_SHA256.try_into().unwrap()); + if p_chk_sum.is_null() { + bail!(ERROR_RDNF_SOLV_CHKSUM); + } + let psz_url = CString::new(url.as_str()).unwrap(); + solv_chksum_add( + p_chk_sum, + psz_url.as_ptr() as *const c_void, + libc::strlen(psz_url.as_ptr()) as i32, + ); + let mut p_cookie: [u8; SOLV_COOKIE_LEN] = [0; SOLV_COOKIE_LEN]; + let mut psz_cookie: [u8; 9] = [0; 9]; + solv_chksum_free(p_chk_sum, p_cookie.as_mut_ptr() as *mut u8); + snprintf( + psz_cookie.as_mut_ptr() as *mut i8, + size_of::<[i8; 9]>(), + "%.2x%.2x%.2x%.2x".as_ptr() as *const i8, + p_cookie[0] as c_uint, + p_cookie[1] as c_uint, + p_cookie[2] as c_uint, + p_cookie[3] as c_uint, + ); + let cookie = CStr::from_ptr(psz_cookie.as_ptr() as *const i8) + .to_str() + .unwrap() + .to_string(); + Ok(format!("{}-{}", name, cookie)) + } +} +pub fn solv_calcuate_cookie_for_file(path: &str) -> Result<[u8; SOLV_COOKIE_LEN]> { + let mut file = std::fs::File::open(path)?; + let mut buf = [0u8; 8192]; + unsafe { + let p_chk_sum = solv_chksum_create(solv_knownid_REPOKEY_TYPE_SHA256.try_into().unwrap()); + if p_chk_sum.is_null() { + bail!(ERROR_RDNF_SOLV_CHKSUM); + } + let ident = CString::new(SOLV_COOKIE_IDENT).unwrap(); + solv_chksum_add( + p_chk_sum, + ident.as_ptr() as *const c_void, + libc::strlen(ident.as_ptr()) as i32, + ); + loop { + let len = file.read(&mut buf)?; + if len <= 0 { + break; + } + solv_chksum_add(p_chk_sum, buf.as_ptr() as *const c_void, len as i32); + buf = [0u8; 8192]; + } + let mut p_cookie: [u8; SOLV_COOKIE_LEN] = [0; SOLV_COOKIE_LEN]; + solv_chksum_free(p_chk_sum, p_cookie.as_mut_ptr() as *mut u8); + Ok(p_cookie) + } +} diff --git a/rdnf/src/sub_command/cache.rs b/rdnf/src/sub_command/cache.rs new file mode 100644 index 00000000..5fbf1a60 --- /dev/null +++ b/rdnf/src/sub_command/cache.rs @@ -0,0 +1,127 @@ +use std::{ + fs::{metadata, read_dir, remove_dir, remove_file}, + path::PathBuf, + time::SystemTime, +}; + +use anyhow::{bail, Result}; +use console::style; + +use crate::{ + default::{CMDLINE_REPO_NAME, REPODATA_DIR_NAME, REPO_METADATA_MARKER, SOLVCACHE_DIR_NAME}, + errors::ERROR_RDNF_CACHE_REFRESH, + utils::check_root, + Rdnf, +}; +impl Rdnf { + pub fn make_cache(&mut self) -> Result<()> { + let mut new_repos = Vec::new(); + for repo in self.repos.clone() { + if repo.psz_name == CMDLINE_REPO_NAME || !repo.base.enabled { + new_repos.push(repo); + continue; + } + if repo.base.lmetadata_expire >= 0 && !self.rc.cli.cacheonly { + match &repo.details.cache_name { + Some(s) => { + let refresh_flag = + self.rc.conf.cachedir.clone() + s.as_str() + "/" + REPO_METADATA_MARKER; + if should_sync_metadata(refresh_flag.as_str(), repo.base.lmetadata_expire)? + { + if check_root().is_err() { + if !self.rc.cli.cacheonly { + bail!(ERROR_RDNF_CACHE_REFRESH); + } + } + let repodata_dir = PathBuf::from( + self.rc.conf.cachedir.clone() + + s.as_str() + + "/" + + REPODATA_DIR_NAME, + ); + recursively_remove_dir(&repodata_dir)?; + let solv_cache_dir = PathBuf::from( + self.rc.conf.cachedir.to_string() + + s.as_str() + + "/" + + SOLVCACHE_DIR_NAME, + ); + recursively_remove_dir(&solv_cache_dir)?; + } + let psz_id = repo.psz_id.clone(); + let status = if repo.base.skip_if_unavailable { + match self.rc.init_repo(repo) { + Ok(s) => { + new_repos.push(s); + format!("{}", style("Done").green()) + } + Err(_) => { + format!("{}", style("Skip").red()) + } + } + } else { + new_repos.push(self.rc.init_repo(repo)?); + format!("{}", style("Done").green()) + }; + if self.rc.cli.refresh { + let (_, width) = self.rc.term.size(); + let offset = (width - 10) as usize; + self.rc.term.write_line(&psz_id)?; + self.rc.term.move_cursor_up(1)?; + self.rc.term.move_cursor_right(offset)?; + self.rc.term.write_line(&status)?; + } + } + None => { + bail!("repo {} enabled,but need baseurl or metalink", repo.psz_id); + } + } + } + } + self.repos = new_repos; + // Ok(self) + Ok(()) + } +} +pub fn should_sync_metadata(f: &str, expire: i128) -> Result { + let should = match metadata(f) { + Ok(m) => { + let mtime = m.modified()?; + let now = SystemTime::now(); + let duration = now.duration_since(mtime)?; + let s = duration.as_secs(); + if s as i128 >= expire { + true + } else { + false + } + } + Err(_) => true, + }; + // if should { + // let file = OpenOptions::new().append(true).create(true).write(true).open(f)?; + // file.write_at(buf, offset) + // } + // let file = ; + Ok(should) +} +pub fn recursively_remove_dir(dir: &PathBuf) -> Result<()> { + if dir.is_dir() { + match read_dir(dir) { + Ok(s) => { + for x in s { + let entry = x?; + let path = entry.path(); + if path.is_dir() { + recursively_remove_dir(&path)?; + } else if path.is_file() { + remove_file(path)?; + } + } + } + Err(_) => {} + } + remove_dir(dir)?; + } + Ok(()) +} diff --git a/rdnf/src/sub_command/info.rs b/rdnf/src/sub_command/info.rs new file mode 100644 index 00000000..5a654f70 --- /dev/null +++ b/rdnf/src/sub_command/info.rs @@ -0,0 +1,143 @@ +use crate::{ + cli::InfoOption, + i18n::pkg_info::{ + PKG_INFO_ARCH___, PKG_INFO_DESC___, PKG_INFO_EPOCH__, PKG_INFO_LICENSE, PKG_INFO_NAME___, + PKG_INFO_RELEASE, PKG_INFO_REPO___, PKG_INFO_SIZE___, PKG_INFO_SUMMARY, PKG_INFO_URL____, + PKG_INFO_VERSION, + }, + solv::rdnf_query::SolvQuery, + Rdnf, +}; +use anyhow::Result; +// #[derive(Clone)] +// pub enum RdnfScope { +// All, +// Installed, +// Available, +// Extras, +// Obsoletes, +// Recent, +// Upgrades, +// DownGrades, +// } + +#[derive(Debug, Clone)] +pub struct PkgInfo { + pub base: PkgInfoBase, + pub details: PkgInfoDetails, + pub other: Option, +} +#[derive(Debug, Clone)] +pub struct PkgInfoBase { + pub epoch: u32, + pub name: String, + pub version: String, + pub release: String, + pub arch: String, + pub evr: String, + pub repo_name: String, +} +#[derive(Debug, Clone)] +pub struct PkgInfoDetails { + pub install_size: u64, + pub formatted_size: String, + pub summary: String, + pub location: Option, +} +#[derive(Debug, Clone)] +pub struct PkgInfoOther { + pub url: String, + pub license: String, + pub description: String, +} +// impl InfoOption { +// pub fn parse_scope(&self){ +// let mut scopes=Vec::new(); + +// } + +// } +impl Rdnf { + pub fn info_command(&mut self, info_opt: InfoOption) -> Result<()> { + self.make_cache()?; + let mut query = SolvQuery::default(self.rc.sack.clone()); + if info_opt.all || info_opt.installed { + query.solv_add_system_repo_filter()?; + } + if info_opt.available { + query.solv_add_available_repo_filter()?; + } + if !info_opt.pkgs.is_empty() { + query.package_names = Some(info_opt.pkgs); + } + query.solv_apply_list_query()?; + match query.solv_get_query_result() { + Ok(pkg_list) => { + let pkg_infos = + PkgInfo::populate_pkg_info(&self.rc.sack, &pkg_list, PkgInfoLevel::Other)?; + for pkg_info in pkg_infos { + let term = &self.rc.term; + term.write_line( + format!("{}\t : {}", PKG_INFO_NAME___, pkg_info.base.name).as_str(), + )?; + term.write_line( + format!("{}\t : {}", PKG_INFO_ARCH___, pkg_info.base.arch).as_str(), + )?; + term.write_line( + format!("{}\t : {}", PKG_INFO_EPOCH__, pkg_info.base.epoch).as_str(), + )?; + term.write_line( + format!("{}\t : {}", PKG_INFO_VERSION, pkg_info.base.version).as_str(), + )?; + term.write_line( + format!("{}\t : {}", PKG_INFO_RELEASE, pkg_info.base.release).as_str(), + )?; + term.write_line( + format!( + "{}\t : {}", + PKG_INFO_SIZE___, pkg_info.details.formatted_size + ) + .as_str(), + )?; + term.write_line( + format!("{}\t : {}", PKG_INFO_REPO___, pkg_info.base.repo_name).as_str(), + )?; + term.write_line( + format!("{}\t : {}", PKG_INFO_SUMMARY, pkg_info.details.summary).as_str(), + )?; + if pkg_info.other.is_some() { + term.write_line( + format!( + "{}\t : {}", + PKG_INFO_URL____, + pkg_info.other.as_ref().unwrap().url + ) + .as_str(), + )?; + term.write_line( + format!( + "{}\t : {}", + PKG_INFO_LICENSE, + pkg_info.other.as_ref().unwrap().license + ) + .as_str(), + )?; + let desc:Vec<&str> = pkg_info.other.as_ref().unwrap().description.split("\n").collect(); + term.write_line(format!("{}\t : {}",PKG_INFO_DESC___,desc[0]).as_str())?; + for item in &desc[1..]{ + term.write_line(format!("\t\t : {}",item).as_str())?; + } + + } + term.write_line("")?; + } + } + Err(_) => {} + }; + Ok(()) + } +} +pub enum PkgInfoLevel { + Details, + Other, +} diff --git a/rdnf/src/sub_command/install.rs b/rdnf/src/sub_command/install.rs new file mode 100644 index 00000000..9352ce1f --- /dev/null +++ b/rdnf/src/sub_command/install.rs @@ -0,0 +1,430 @@ +use std::{ + ffi::CString, + fs::{create_dir_all, metadata}, + path::Path, +}; + +use crate::{ + c_lib::{queue_push, queue_empty, get_queue_element_value}, + cli::AlterOption, + default::{CMDLINE_REPO_NAME, RPM_CACHE_DIR_NAME, GPGKEY_CACHE_DIR_NAME}, + errors::{ + ERROR_RDNF_INVALID_PARAMETER, ERROR_RDNF_NOTHING_TO_DO, ERROR_RDNF_NO_MATCH, + ERROR_RDNF_REPO_NOT_FOUND, ERROR_RDNF_URL_INVALID, + }, + goal::SolvedPkgInfo, + solv::rdnf_query::init_queue, + Rdnf, +}; +use anyhow::{bail, Result}; +use console::style; +use dialoguer::{theme::ColorfulTheme, Confirm}; +use solv_sys::ffi::{ + pool_createwhatprovides, repo_add_rpm, repo_internalize, s_Queue, Queue, REPO_NO_INTERNALIZE, + REPO_REUSE_REPODATA, RPM_ADD_WITH_HDRID, RPM_ADD_WITH_SHA256SUM, +}; + +use super::{repo::RepoData, repoutils::download_file}; +impl Rdnf { + pub fn alter_command( + &mut self, + pkgs: Vec, + alter_type: AlterType, + alter_args: &AlterOption, + ) -> Result<()> { + let solved_pkg_info = self.resolve(&pkgs, alter_type.clone(), alter_args)?; + let silent = alter_args.quiet && alter_args.assume_yes; + if !silent && !solved_pkg_info.not_resolved.is_empty() { + for pkg_name in &solved_pkg_info.not_resolved { + self.rc + .term + .write_line(&format!("No package {} available", style(pkg_name).red()))?; + } + } + if solved_pkg_info.need_action == 0 { + if solved_pkg_info.not_resolved.is_empty() { + bail!(ERROR_RDNF_NO_MATCH); + } else { + bail!(ERROR_RDNF_NOTHING_TO_DO); + } + } + if !silent { + solved_pkg_info.print(&self.rc.term)?; + if alter_args.download_only { + self.rc + .term + .write_line("rdnf will only download packages needed for the transaction")?; + } + } + if solved_pkg_info.need_action > 0 { + if Confirm::with_theme(&ColorfulTheme::default()) + .with_prompt("Is this ok") + .interact() + .unwrap() + { + if !silent && solved_pkg_info.need_download > 0 { + self.rc.term.write_line("Downloading:")?; + } + self.rpm_exec_transaction(&solved_pkg_info, &alter_type, alter_args)?; + } + } + Ok(()) + } + pub fn resolve( + &mut self, + pkgs: &Vec, + alter_type: AlterType, + alter_args: &AlterOption, + ) -> Result { + let mut queue_goal = init_queue(); + let mut not_resolved = Vec::::new(); + if alter_type.is_install() || alter_type.is_reinstall() { + self.add_cmdline_pkgs(&pkgs, &mut queue_goal as *mut Queue)?; + } + self.make_cache()?; + self.prepare_all_pkgs(alter_type.clone(), pkgs, &mut not_resolved, &mut queue_goal)?; + let solved_pkg_info_base = self.goal(&mut queue_goal, alter_type.clone(), alter_args)?; + solved_pkg_info_base.check_protected_pkgs()?; + Ok(SolvedPkgInfo { + need_action: solved_pkg_info_base.get_need_action(), + need_download: solved_pkg_info_base.get_need_download(), + not_available: None, + existing: None, + not_resolved, + not_installed: None, + base: solved_pkg_info_base, + }) + } + + pub fn add_cmdline_pkgs(&self, pkgs: &Vec, queue: *mut s_Queue) -> Result<()> { + for pkg in pkgs { + let rpm_path = if Path::new(pkg.as_str()).exists() && pkg.ends_with(".rpm") { + pkg.clone() + } else { + if !is_remote_url(pkg.as_str()) { + if !pkg.starts_with("file://") { + continue; + } else { + let k = pkg.split_once("file://").unwrap().1; + if k == "" || k.matches("#").collect::>().len() > 0 { + bail!(ERROR_RDNF_URL_INVALID); + }; + "/".to_string() + k.split_once("/").unwrap().1 + } + } else { + let pkg_name = pkg.rsplit_once("/").unwrap().1; + match self.repos.iter().find(|x| x.psz_id == CMDLINE_REPO_NAME) { + Some(repo) => self.download_pkg_to_cache( + pkg.as_str(), + pkg_name, + repo, + RPM_CACHE_DIR_NAME, + )?, + None => { + bail!(ERROR_RDNF_REPO_NOT_FOUND) + } + } + } + }; + unsafe { + let file_path = CString::new(rpm_path.as_str()).unwrap(); + let id = repo_add_rpm( + self.solv_cmdline_repo, + file_path.as_ptr(), + (REPO_REUSE_REPODATA + | REPO_NO_INTERNALIZE + | RPM_ADD_WITH_HDRID + | RPM_ADD_WITH_SHA256SUM) + .try_into() + .unwrap(), + ); + if id == 0 { + bail!(ERROR_RDNF_INVALID_PARAMETER) + } + queue_push(queue, id); + } + } + unsafe { + pool_createwhatprovides(self.rc.sack.pool); + repo_internalize(self.solv_cmdline_repo); + } + Ok(()) + } + pub fn prepare_all_pkgs( + &self, + alter_type: AlterType, + pkgs: &Vec, + not_resolved: &mut Vec, + queue_goal: *mut Queue, + ) -> Result<()> { + let mut queue_local = init_queue(); + match alter_type { + AlterType::DownGradeAll => { + //TODO + } + AlterType::AutoEraseAll => { + //TODO + } + _ => {} + } + let cli = &self.rc.cli; + if (alter_type.is_upgrade_all() || alter_type.is_upgrade()) + && (cli.security || cli.sec_severity.is_some() || cli.reboot_required) + { + let pkgs=self.get_update_pkgs()?; + for pkg_name in pkgs { + self.prepare_single_pkg(pkg_name.as_str(), AlterType::Upgrade, not_resolved, queue_goal)?; + } + } else { + for pkg in pkgs { + if is_glob(pkg.as_str()) { + queue_empty(&mut queue_local); + self.rc.sack.get_glob_pkgs(pkg, &mut queue_local)?; + if queue_local.count ==0{ + not_resolved.push(pkg.to_string()); + }else{ + for index in 0..queue_local.count{ + let id=get_queue_element_value(&queue_local, index as u32); + let pkg_name=self.rc.sack.solv_get_pkg_name_by_id(id)?; + self.prepare_single_pkg(pkg_name.as_str(), alter_type.clone(), not_resolved, queue_goal)?; + } + } + } else { + if Path::new(pkg.as_str()).exists() && pkg.ends_with(".rpm") { + continue; + } + if is_remote_url(pkg.as_str()) || pkg.starts_with("file://") { + continue; + } + self.prepare_single_pkg( + pkg.as_str(), + alter_type.clone(), + not_resolved, + queue_goal, + )?; + } + } + }; + Ok(()) + } + + pub fn prepare_single_pkg( + &self, + pkg_name: &str, + alter_type: AlterType, + not_resolved: &mut Vec, + queue_goal: *mut Queue, + ) -> Result<()> { + match self.rc.sack.solv_count_pkg_by_name(pkg_name) { + Ok(0) => { + not_resolved.push(pkg_name.to_string()); + return Ok(()); + } + Ok(count) => count, + Err(_) => { + bail!("{} package not found or not installed", pkg_name) + } + }; + match alter_type { + AlterType::ReInstall => { + self.rc.sack.add_pkgs_for_reinstall(queue_goal, pkg_name)?; + } + AlterType::Erase | AlterType::AutoErase => { + self.rc.sack.add_pkgs_for_erase(queue_goal, pkg_name)?; + } + AlterType::Install => { + if !self.rc.sack.add_pkgs_for_install(queue_goal, pkg_name)? { + println!("Package {} is already installed", pkg_name); + } + } + AlterType::Upgrade => { + self.rc.sack.add_pkgs_for_upgrade(queue_goal, pkg_name)?; + } + AlterType::DownGradeAll | AlterType::DownGrade => {} + _ => {} + } + Ok(()) + } +} +pub fn is_remote_url(url: &str) -> bool { + let mut is_url = false; + for m in ["http://", "https://", "ftp://", "ftps://"] { + if url.starts_with(m) { + is_url = true; + break; + }; + } + is_url +} +pub fn is_glob(s: &str) -> bool { + for ele in s.chars() { + if ele == '*' || ele == '?' || ele == '[' { + return true; + } + } + false +} + +impl Rdnf { + pub fn download_pkg_to_cache( + &self, + url: &str, + pkg_name: &str, + repo: &RepoData, + dir: &str, + ) -> Result { + let rpm_cache_dir = self.rc.conf.cachedir.clone() + repo.psz_id.as_str() + "/" + dir + "/"; + let u = match url.split_once("://") { + Some((_, s)) => match s.split_once("/") { + Some((_, s)) => s, + None => { + bail!(ERROR_RDNF_URL_INVALID) + } + }, + None => { + bail!(ERROR_RDNF_URL_INVALID) + } + }; + let rpm_cache_file = rpm_cache_dir + u; + let download_cache_dir = rpm_cache_file + .rsplit_once("/") + .unwrap() + .0 + .trim_end_matches("/"); + let rpm_cache_file = download_cache_dir.to_string() + "/" + pkg_name; + create_dir_all(download_cache_dir)?; + let (_, width) = self.rc.term.size(); + let repo_width = (width as f32 * 0.6) as usize; + let flag_width = width as usize - repo_width - 4; + + if Path::new(rpm_cache_file.as_str()).exists() { + let m = metadata(rpm_cache_file.as_str())?; + if m.len() > 0 { + let item = format!( + "{:rest$}{:>4}", + pkg_name, + style("exists").green(), + "", + width = repo_width, + rest = flag_width + ); + self.rc.term.write_line(item.as_str())?; + return Ok(rpm_cache_file); + } + } + download_file(&self.rc, repo, url, rpm_cache_file.as_str(), pkg_name)?; + let item = format!( + "{:rest$}{:>4}", + pkg_name, + style("downloaded").green(), + "", + width = repo_width, + rest = flag_width + ); + self.rc.term.write_line(item.as_str())?; + Ok(rpm_cache_file) + } + pub fn download_key_to_cache( + &self, + url: &str, + repo: &RepoData, + ) -> Result { + let (_,rest) = url.split_once("://").unwrap(); + let rest = match rest.rsplit_once("/") { + Some((_,r)) => {r}, + None => {rest}, + }; + let file_path=self.rc.conf.cachedir.to_string()+repo.psz_id.as_str()+"/"+GPGKEY_CACHE_DIR_NAME+"/"+rest; + let (_, width) = self.rc.term.size(); + let repo_width = (width as f32 * 0.6) as usize-" gpgkey".len(); + let flag_width = width as usize - repo_width - 4; + download_file(&self.rc, repo, url, file_path.as_str(), repo.psz_id.as_str())?; + let item = format!( + "{:rest$}{:>4}", + repo.psz_id, + style("downloaded").green(), + "", + width = repo_width, + rest = flag_width + ); + self.rc.term.write_line(item.as_str())?; + Ok(file_path) + } +} + +#[derive(Debug, Clone)] +pub enum AlterType { + AutoErase, + AutoEraseAll, + DownGrade, + DownGradeAll, + Erase, + Install, + ReInstall, + Upgrade, + UpgradeAll, + DistroSync, + Obsoleted, +} +impl AlterType { + pub fn is_auto_erase(&self) -> bool { + match self { + Self::AutoErase => true, + _ => false, + } + } + pub fn is_auto_erase_all(&self) -> bool { + match self { + Self::AutoEraseAll => true, + _ => false, + } + } + pub fn is_down_grade(&self) -> bool { + match self { + Self::DownGradeAll => true, + _ => false, + } + } + pub fn is_erase(&self) -> bool { + match self { + Self::Erase => true, + _ => false, + } + } + pub fn is_install(&self) -> bool { + match self { + Self::Install => true, + _ => false, + } + } + pub fn is_reinstall(&self) -> bool { + match self { + Self::ReInstall => true, + _ => false, + } + } + pub fn is_upgrade(&self) -> bool { + match self { + Self::Upgrade => true, + _ => false, + } + } + pub fn is_upgrade_all(&self) -> bool { + match self { + Self::UpgradeAll => true, + _ => false, + } + } + pub fn is_distro_sync(&self) -> bool { + match self { + Self::DistroSync => true, + _ => false, + } + } + pub fn is_obsoleted(&self) -> bool { + match self { + Self::Obsoleted => true, + _ => false, + } + } +} diff --git a/rdnf/src/sub_command/mod.rs b/rdnf/src/sub_command/mod.rs new file mode 100644 index 00000000..24e367b0 --- /dev/null +++ b/rdnf/src/sub_command/mod.rs @@ -0,0 +1,7 @@ +pub mod repo; +pub mod cache; +pub mod repoutils; +pub mod search; +pub mod install; +pub mod update; +pub mod info; \ No newline at end of file diff --git a/rdnf/src/sub_command/repo.rs b/rdnf/src/sub_command/repo.rs new file mode 100644 index 00000000..4fce7ccb --- /dev/null +++ b/rdnf/src/sub_command/repo.rs @@ -0,0 +1,514 @@ +use std::{ffi::CString, os::raw::c_void}; + +use anyhow::{bail, Result}; +use config::Config; +use console::style; +use glob::Pattern; +use solv_sys::ffi::{pool_createwhatprovides, repo_create, Repo}; + +use crate::solv::sack::solv_create_cache_name; +use crate::solv::sack::Solvsack; +use crate::{ + cli::Commands, + conf::ConfigMain, + default::{VAR_BASEARCH, VAR_RELEASEVER}, + errors::ERROR_RDNF_INVALID_PARAMETER, + i18n::repo_list::{ + REPOLIST_REPO_ID, REPOLIST_REPO_NAME, REPOLIST_REPO_STATUS, REPOLIST_REPO_STATUS_DISABLED, + REPOLIST_REPO_STATUS_ENABLED, + }, + solv::SolvRepoInfoInernal, + utils::check_dir, + Cli, Rdnf, +}; + +const CMDLINE_REPO_NAME: &str = "@cmdline"; +pub enum RepoListFilter { + All, + Enabled, + Disabled, +} + +impl Rdnf { + pub fn repo_list(self) -> Result<()> { + match self.rc.cli.command { + Commands::Repolist(opt) => { + let sum = opt.all as usize + opt.enabled as usize + opt.disabled as usize; + if sum > 1 { + bail!("you can only choose one of three options (all,enabled,disabled)") + }; + let filter = match opt.all { + true => RepoListFilter::All, + false => match opt.enabled { + true => RepoListFilter::Enabled, + false => match opt.disabled { + true => RepoListFilter::Disabled, + false => RepoListFilter::Enabled, + }, + }, + }; + let (_heigt, width) = self.rc.term.size(); + let l = (width as f32 * 0.3) as usize; + let c = (width as f32 * 0.5) as usize; + let r = width as usize - l - c; + let title = format!( + "{: true, + RepoListFilter::Enabled => repo.base.enabled, + RepoListFilter::Disabled => !repo.base.enabled, + }; + if repo.psz_name == CMDLINE_REPO_NAME { + is_show = false; + } + if is_show { + let status = match repo.base.enabled { + true => { + format!("{}", style(REPOLIST_REPO_STATUS_ENABLED).green()) + } + false => { + format!("{}", style(REPOLIST_REPO_STATUS_DISABLED).red()) + } + }; + let item = format!( + "{: {} + } + Ok(()) + } +} +pub fn load_repo_data(config: &ConfigMain, filter: RepoListFilter) -> Result> { + let repo_cmdline = RepoData::create_repo(CMDLINE_REPO_NAME); + let mut repodatas: Vec = Vec::new(); + repodatas.push(repo_cmdline); + let dir = config.repodir.as_str(); + check_dir(dir)?; + match glob::glob((dir.to_string() + "*.repo").as_str()) { + Ok(paths) => { + match Config::builder() + .add_source( + paths + .map(|p| config::File::from(p.unwrap()).format(config::FileFormat::Ini)) + .collect::>(), + ) + .build() + { + Ok(c) => { + for (psz_id, value) in c + .try_deserialize::>() + .unwrap() + { + let mut repo = RepoData::create_repo(psz_id.as_str()); + for (key, v) in value.into_table().unwrap() { + repo.update_repo(key.as_str(), v)? + } + match filter { + RepoListFilter::All => repodatas.push(repo), + RepoListFilter::Enabled => { + if repo.base.enabled { + repodatas.push(repo) + } + } + RepoListFilter::Disabled => { + if !repo.base.enabled { + repodatas.push(repo) + } + } + }; + } + } + Err(_) => { + bail!("Failed to parse config files .repo dir {}", dir) + } + }; + } + Err(_) => { + bail!("Failed to search .repo files in dir {}", dir) + } + }; + Ok(repodatas) +} +pub fn repo_list_finalize( + cli: &mut Cli, + config: &ConfigMain, + repos: &mut Vec, +) -> Result<()> { + match &cli.repoid { + Some(v) => { + alter_repo_state_enable(repos, false, "*")?; + for pattern in v { + alter_repo_state_enable(repos, true, pattern)?; + } + } + None => {} + } + match &cli.enablerepo { + Some(v) => { + for pattern in v { + alter_repo_state_enable(repos, true, pattern)?; + } + } + None => {} + } + match &cli.disablerepo { + Some(v) => { + for pattern in v { + alter_repo_state_enable(repos, false, pattern)?; + } + } + None => {} + } + for repo in repos { + repo.psz_name = repo + .psz_name + .replace(VAR_RELEASEVER, &config.var_release_ver) + .replace(VAR_BASEARCH, &config.var_base_arch); + match &repo.details.base_url { + Some(s) => { + repo.details.base_url = Some( + s.replace(VAR_RELEASEVER, &config.var_release_ver) + .replace(VAR_BASEARCH, &config.var_base_arch), + ); + } + None => {} + } + match &repo.details.meta_link { + Some(s) => { + let meta_link = s + .replace(VAR_RELEASEVER, &config.var_release_ver) + .replace(VAR_BASEARCH, &config.var_base_arch); + repo.details.cache_name = Some(solv_create_cache_name(&repo.psz_id, &meta_link)?); + repo.details.meta_link = Some(meta_link); + } + None => match &repo.details.base_url { + Some(s) => { + repo.details.cache_name = Some(solv_create_cache_name(&repo.psz_id, s)?); + } + None => {} + }, + } + match &repo.details.url_gpg_keys { + Some(s) => { + let mut url_gpg_keys=Vec::new(); + for ele in s { + let m=ele.replace(VAR_RELEASEVER, &config.var_release_ver) + .replace(VAR_BASEARCH, &config.var_base_arch); + url_gpg_keys.push(m); + } + repo.details.url_gpg_keys=Some(url_gpg_keys); + }, + None => {}, + } + } + Ok(()) +} +pub fn init_cmdline_repo(sack: &mut Solvsack) -> Result<*mut Repo> { + unsafe { + let repo_name = CString::new(CMDLINE_REPO_NAME).unwrap(); + let repo = repo_create(sack.pool, repo_name.as_ptr()); + if repo.is_null() { + bail!(ERROR_RDNF_INVALID_PARAMETER); + } + let solv_repo_info = SolvRepoInfoInernal { + repo, + cookie: None, + n_cookie_set: None, + repo_cache_dir: None, + }; + let p = Box::into_raw(Box::new(solv_repo_info)) as *mut c_void; + (*repo).appdata = p; + pool_createwhatprovides(sack.pool); + Ok(repo) + } +} +fn alter_repo_state_enable(repos: &mut Vec, enable: bool, pattern: &str) -> Result<()> { + match Pattern::new(pattern) { + Ok(p) => { + for repo in &mut *repos { + if p.matches(&repo.psz_id) { + repo.base.enabled = enable; + } + } + } + Err(e) => { + bail!("Failed to enablerepo {}, because {}", &pattern, e) + } + } + Ok(()) +} + +#[derive(Debug, Clone, Copy)] +pub struct RepoDataBase { + pub enabled: bool, + pub skip_if_unavailable: bool, + pub gpgcheck: bool, + pub priority: i32, + pub timeout: u64, + pub retries: i32, + pub minrate: u32, + pub throttle: u64, + pub sslverify: bool, + pub lmetadata_expire: i128, + pub skip_md_filelists: bool, + pub skip_md_updateinfo: bool, + pub skip_md_other: bool, +} +impl RepoDataBase { + pub fn default() -> Self { + RepoDataBase { + enabled: true, + skip_if_unavailable: false, + gpgcheck: true, + sslverify: true, + lmetadata_expire: 172800, + priority: 50, + timeout: 0, + minrate: 0, + throttle: 0, + retries: 10, + skip_md_filelists: false, + skip_md_updateinfo: false, + skip_md_other: false, + } + } +} +#[derive(Debug, Clone)] +pub struct RepoDataDetails { + pub base_url: Option, + pub meta_link: Option, + pub url_gpg_keys: Option>, + pub username: Option, + pub password: Option, + pub ssl_ca_cert: Option, + pub ssl_client_cert: Option, + pub ssl_client_key: Option, + pub cache_name: Option, +} +impl RepoDataDetails { + pub fn new() -> Self { + RepoDataDetails { + cache_name: None, + base_url: None, + meta_link: None, + url_gpg_keys: None, + username: None, + password: None, + ssl_ca_cert: None, + ssl_client_cert: None, + ssl_client_key: None, + } + } +} +#[derive(Debug, Clone)] +pub struct RepoData { + pub base: RepoDataBase, + pub psz_id: String, + pub psz_name: String, + pub details: RepoDataDetails, +} +impl RepoData { + pub fn create_repo(psz_id: &str) -> Self { + RepoData { + base: RepoDataBase::default(), + psz_id: String::from(psz_id), + psz_name: String::from(psz_id), + details: RepoDataDetails::new(), + } + } + pub fn update_repo(self: &mut Self, key: &str, v: config::Value) -> Result<()> { + match key { + "enabled" => match v.into_bool() { + Ok(b) => self.base.enabled = b, + Err(_) => { + bail!("repo {} enabled should be 0 or 1", self.psz_id) + } + }, + "name" => match v.into_string() { + Ok(s) => self.psz_name = s, + Err(_) => { + bail!("repo {} name should be String", self.psz_id) + } + }, + "baseurl" => match v.into_string() { + Ok(s) => self.details.base_url = Some(s), + Err(_) => { + bail!("repo {} baseurl should be String", self.psz_id) + } + }, + "metalink" => match v.into_string() { + Ok(s) => self.details.meta_link = Some(s), + Err(_) => { + bail!("repo {} metalink should be String", self.psz_id) + } + }, + "skip_if_unavailable" => match v.into_bool() { + Ok(b) => self.base.skip_if_unavailable = b, + Err(_) => { + bail!("repo {} skip_if_unavailable should be 0 or 1", self.psz_id) + } + }, + "gpgcheck" => match v.into_bool() { + Ok(b) => self.base.gpgcheck = b, + Err(_) => { + bail!("repo {} gpgcheck should be 0 or 1", self.psz_id) + } + }, + "gpgkey" => match v.into_string() { + Ok(s) => { + let mut gpg_keys: Vec = Vec::new(); + for ele in s.split(" ").collect::>() { + gpg_keys.push(String::from(ele)); + } + self.details.url_gpg_keys = Some(gpg_keys); + } + Err(_) => { + bail!( + "repo {} gpgkey should be string array split by ' ' ", + self.psz_id + ) + } + }, + "username" => match v.into_string() { + Ok(s) => self.details.username = Some(s), + Err(_) => { + bail!("repo {} username should be string", self.psz_id) + } + }, + "password" => match v.into_string() { + Ok(s) => self.details.password = Some(s), + Err(_) => { + bail!("repo {} password should be string", self.psz_id) + } + }, + "priority" => match v.into_int() { + Ok(i) => self.base.priority = i as i32, + Err(_) => { + bail!("repo {} priority should be int32", self.psz_id) + } + }, + "timeout" => match v.into_int() { + Ok(i) => self.base.timeout = i as u64, + Err(_) => { + bail!("repo {} timeout should be int32", self.psz_id) + } + }, + "retries" => match v.into_int() { + Ok(i) => self.base.retries = i as i32, + Err(_) => { + bail!("repo {} retries should be int32", self.psz_id) + } + }, + "minrate" => match v.into_int() { + Ok(i) => self.base.minrate = i as u32, + Err(_) => { + bail!("repo {} minrate should be int32", self.psz_id) + } + }, + "throttle" => match v.into_int() { + Ok(i) => self.base.throttle = i as u64, + Err(_) => { + bail!("repo {} throttle should be int32", self.psz_id) + } + }, + "sslverify" => match v.into_bool() { + Ok(b) => self.base.sslverify = b, + Err(_) => { + bail!("repo {} sslverify should be 0 or 1", self.psz_id) + } + }, + "sslcacert" => match v.into_string() { + Ok(s) => self.details.ssl_ca_cert = Some(s), + Err(_) => { + bail!("repo {} sslcacert should be string", self.psz_id) + } + }, + "sslclientcert" => match v.into_string() { + Ok(s) => self.details.ssl_client_cert = Some(s), + Err(_) => { + bail!("repo {} sslckuebtcert should be string", self.psz_id) + } + }, + "sslclientkey" => match v.into_string() { + Ok(s) => self.details.ssl_client_key = Some(s), + Err(_) => { + bail!("repo {} sslclientkey should be string", self.psz_id) + } + }, + "metadata_expire" => match v.into_string() { + Ok(s) => { + if s == "never" { + self.base.lmetadata_expire = -1; + } else { + self.base.lmetadata_expire = match s.parse::() { + Ok(t) => t, + Err(_) => { + let (num, mul) = s.split_at(s.len() - 1); + let n = match num.parse::() { + Ok(n) => n, + Err(_) => { + bail!("repo {} metadata_expire should be like 1 or 1d or 1h or 1m or 1s",self.psz_id) + } + }; + match mul { + "s" => n, + "m" => 60 * n, + "h" => 60 * 60 * n, + "d" => 60 * 60 * 24 * n, + _ => { + bail!("repo {} metadata_expire the unit of time should be d,h,m,s(default)",self.psz_id) + } + } + } + } + } + } + Err(_) => { + bail!( + "repo {} metadata_expire can't be parse to string ", + self.psz_id + ) + } + }, + "skip_md_filelists" => match v.into_bool() { + Ok(b) => self.base.skip_md_filelists = b, + Err(_) => { + bail!("repo {} skip_md_filelists should be 0 or 1", self.psz_id) + } + }, + "skip_md_updateinfo" => match v.into_bool() { + Ok(b) => self.base.skip_md_updateinfo = b, + Err(_) => { + bail!("repo {} skip_md_updateinfo should be 0 or 1", self.psz_id) + } + }, + "skip_md_other" => match v.into_bool() { + Ok(b) => self.base.skip_md_other = b, + Err(_) => { + bail!("repo {} skip_md_other should be 0 or 1", self.psz_id) + } + }, + _ => {} + } + Ok(()) + } +} diff --git a/rdnf/src/sub_command/repoutils.rs b/rdnf/src/sub_command/repoutils.rs new file mode 100644 index 00000000..9113e5f9 --- /dev/null +++ b/rdnf/src/sub_command/repoutils.rs @@ -0,0 +1,478 @@ +use crate::{ + default::{ + REPODATA_DIR_NAME, REPO_BASEURL_FILE_NAME, REPO_METADATA_FILE_NAME, + REPO_METADATA_FILE_PATH, REPO_METADATA_MARKER, REPO_METALINK_FILE_NAME, RPM_CACHE_DIR_NAME, + SOLVCACHE_DIR_NAME, SOLV_COOKIE_LEN, + }, + errors::ERROR_RDNF_INVALID_PARAMETER, + metalink::MetalinkContext, + repomd::RepoMd, + solv::{ + rdnf_repo::{solv_create_metadata_cache, solv_read_yum_repo, solv_user_metadata_cache}, + sack::solv_calcuate_cookie_for_file, + SolvRepoInfoInernal, + }, + RdnfContext, +}; +use anyhow::{bail, Result}; +use console::Term; +use curl::easy::{Easy2, Handler}; +use indicatif::{ProgressBar, ProgressStyle}; +use libc::c_void; +use md5::Digest; +use md5::Md5; +use sha1::Sha1; +use sha2::{Sha256, Sha512}; +use solv_sys::ffi::{pool_createwhatprovides, repo_create}; +use std::{ + ffi::CString, + fs::{create_dir_all, remove_file, rename, File, OpenOptions}, + io::{self, Read, Write}, + path::{Path, PathBuf}, + time::Duration, +}; + +use super::{cache::recursively_remove_dir, repo::RepoData}; + +impl RdnfContext { + pub fn init_repo(&self, repo: RepoData) -> Result { + let cache_name = repo.details.cache_name.as_ref().unwrap().as_str(); + let repo_cache_dir = self.conf.cachedir.clone() + cache_name + "/"; + let mut repo = repo; + let repo_md = self.get_repo_md(&mut repo, repo_cache_dir.clone())?; + let pool = self.sack.pool; + let id_ptr = CString::new(repo.psz_id.as_str()).unwrap(); + unsafe { + let p_repo = repo_create(pool, id_ptr.as_ptr()); + if p_repo.is_null() { + bail!(ERROR_RDNF_INVALID_PARAMETER); + } + let repo_md_file = repo_cache_dir.clone() + REPO_METADATA_FILE_PATH; + let cookie = solv_calcuate_cookie_for_file(repo_md_file.as_str())?; + let solv_repo_info = SolvRepoInfoInernal { + repo: p_repo, + cookie: Some(cookie), + n_cookie_set: Some(1), + repo_cache_dir: Some(repo_cache_dir.clone()), + }; + let p = Box::into_raw(Box::new(&solv_repo_info)) as *mut c_void; + (*p_repo).appdata = p; + let psz_cache_file_path = + repo_cache_dir.clone() + SOLVCACHE_DIR_NAME + "/" + repo.psz_id.as_str() + ".solv"; + if !solv_user_metadata_cache(&solv_repo_info, psz_cache_file_path.as_str())? { + solv_read_yum_repo(&solv_repo_info.repo, repo_md_file.clone(), repo_md)?; + solv_create_metadata_cache(&solv_repo_info, repo.psz_id.as_str())?; + } + pool_createwhatprovides(self.sack.pool); + } + Ok(repo) + } + pub fn get_repo_md(&self, repo: &mut RepoData, repo_cache_dir: String) -> Result { + let mut replace_repo_md = false; + let metalink = repo.details.meta_link.is_some(); + let keepcache = self.conf.keepcache; + let mut replace_base_url = false; + let mut new_repo_md_file = false; + let mut cookie: [u8; SOLV_COOKIE_LEN] = [0; SOLV_COOKIE_LEN]; + if repo.details.base_url.is_none() && repo.details.meta_link.is_none() { + bail!("Cannot find a valid base URL for repo: {}", repo.psz_name); + } + let repo_data_dir = repo_cache_dir.clone() + REPODATA_DIR_NAME + "/"; + let solv_cache_dir = repo_cache_dir.clone() + SOLVCACHE_DIR_NAME + "/"; + let last_refresh_marker = repo_cache_dir.clone() + REPO_METADATA_MARKER; + let rpms_cache_dir = repo_cache_dir.clone() + RPM_CACHE_DIR_NAME + "/"; + let repo_md_file = repo_data_dir.clone() + REPO_METADATA_FILE_NAME; + let meta_link_file = repo_data_dir.clone() + REPO_METALINK_FILE_NAME; + let base_url_file = repo_data_dir.clone() + REPO_BASEURL_FILE_NAME; + let tmp_repo_data_dir = repo_cache_dir.clone() + "tmp/"; + let tmp_repo_md_file = tmp_repo_data_dir.clone() + REPO_METADATA_FILE_NAME; + let tmp_meta_link_file = tmp_repo_data_dir.clone() + REPO_METALINK_FILE_NAME; + let tmp_base_url_file = tmp_repo_data_dir.clone() + REPO_BASEURL_FILE_NAME; + let mut need_download = if repo.details.meta_link.is_some() { + !Path::new(meta_link_file.as_str()).exists() + && !Path::new(base_url_file.as_str()).exists() + } else { + !Path::new(repo_md_file.as_str()).exists() + }; + if self.cli.refresh { + if repo.details.meta_link.is_some() { + if Path::new(&meta_link_file).exists() { + cookie = solv_calcuate_cookie_for_file(meta_link_file.as_str())?; + } + } else { + if Path::new(&repo_md_file).exists() { + cookie = solv_calcuate_cookie_for_file(repo_md_file.as_str())?; + } + } + need_download = true; + } + + if need_download && !self.cli.cacheonly { + create_dir_all(&tmp_repo_data_dir)?; + if metalink { + let url = repo.details.meta_link.clone().unwrap(); + download_file( + &self, + repo, + url.as_str(), + tmp_meta_link_file.as_str(), + &repo.psz_name, + )?; + let mut mc = match MetalinkContext::from_with_filename( + &tmp_meta_link_file, + REPO_METADATA_FILE_NAME, + ) { + Ok(s) => s, + Err(_) => { + bail!("check {}.repo metalink url", repo.psz_id) + } + }; + + replace_repo_md = true; + if cookie[0] != 0 { + let tmp_cookie = solv_calcuate_cookie_for_file(tmp_meta_link_file.as_str())?; + if tmp_cookie == cookie { + replace_repo_md = false; + } + } + if replace_repo_md { + let mut choose_url = None; + for ele in mc.urls { + ele.url.ends_with(REPO_METADATA_FILE_PATH); + if download_file( + &self, + repo, + ele.url.as_str(), + tmp_repo_md_file.as_str(), + repo.psz_name.as_str(), + ) + .is_ok() + { + choose_url = Some(ele.url.clone()); + break; + }; + } + match choose_url { + Some(choose_url) => { + let tmp_base_url_file = + tmp_repo_data_dir.clone() + REPO_BASEURL_FILE_NAME; + let baseurl = choose_url.trim_end_matches(REPO_METADATA_FILE_PATH); + repo.details.base_url = Some(baseurl.to_string()); + if Path::new(&tmp_base_url_file).exists() { + remove_file(&tmp_base_url_file)?; + } + OpenOptions::new() + .write(true) + .create(true) + .open(tmp_base_url_file)? + .write_all(baseurl.as_bytes())?; + mc.hashs.sort_by(|a, b| a.value.cmp(&b.value)); + let mut flag = mc.hashs.len(); + for ele in mc.hashs { + if ele + .kind + .checksum(tmp_repo_md_file.as_str(), ele.value.as_str())? + { + break; + }; + flag -= 1; + } + if flag == 0 { + bail!("{}.repo repomd.xml invalid", repo.psz_id); + } + } + None => { + bail!("{}.repo metalink don't have vaild url ", repo.psz_id) + } + } + replace_base_url = true; + new_repo_md_file = true; + + if Path::new(&repo_md_file).exists() { + let cookie = solv_calcuate_cookie_for_file(repo_md_file.as_str())?; + let tmp_cookie = solv_calcuate_cookie_for_file(tmp_repo_md_file.as_str())?; + if cookie == tmp_cookie { + replace_repo_md = false; + } + } + } + } else { + let url = repo + .details + .base_url + .clone() + .unwrap() + .trim_end_matches("/") + .to_string() + + "/" + + REPO_METADATA_FILE_PATH; + download_file( + &self, + repo, + url.as_str(), + tmp_repo_md_file.as_str(), + repo.psz_name.as_str(), + )?; + replace_repo_md = true; + if cookie[0] != 0 { + let tmp_cookie = solv_calcuate_cookie_for_file(tmp_repo_md_file.as_str())?; + if tmp_cookie == cookie { + replace_repo_md = false; + } + } + new_repo_md_file = true; + } + } + + if metalink && !replace_base_url && Path::new(base_url_file.as_str()).exists() { + let mut buf = String::new(); + File::open(base_url_file.clone()) + .unwrap() + .read_to_string(&mut buf)?; + repo.details.base_url = Some(buf.trim_end_matches("/").to_string() + "/"); + } + if replace_repo_md { + recursively_remove_dir(&PathBuf::from(repo_data_dir.clone()))?; + recursively_remove_dir(&PathBuf::from(solv_cache_dir))?; + match remove_file(last_refresh_marker.clone()) { + Ok(_) => {} + Err(_) => {} + }; + if !keepcache { + recursively_remove_dir(&PathBuf::from(rpms_cache_dir))?; + } + create_dir_all(repo_data_dir)?; + rename(tmp_repo_md_file, repo_md_file.clone())?; + } + if new_repo_md_file { + OpenOptions::new() + .write(true) + .create(true) + .open(last_refresh_marker)?; + } + if metalink && replace_base_url { + rename(tmp_meta_link_file, meta_link_file)?; + rename(tmp_base_url_file, base_url_file)?; + } + + let repo_md = RepoMd::parse_from(repo_md_file.as_str())?; + let repo_md = repo_md.ensure_repo_md_parts(&self, repo, repo_cache_dir.clone())?; + + Ok(repo_md) + } +} +// impl RdnfContext { +pub fn download_file( + rc: &RdnfContext, + repo: &RepoData, + url: &str, + file: &str, + msg: &str, +) -> Result<()> { + let pb = rc.multi_process.add(ProgressBar::new(0)); + let (_, width) = Term::stdout().size(); + let style = format!("{{msg:{}}}{{spinner:.green}}[{{bar:{}.cyan/blue}}] {{bytes}}/{{total_bytes}} ({{bytes_per_sec}},{{eta}})",width /3,width/3); + let style = ProgressStyle::with_template(style.as_str()) + .unwrap() + .progress_chars("#>-"); + pb.set_style(style); + pb.set_message(msg.to_string()); + let file_path = format!("{}.tmp", file); + let mut easy = Easy2::new(Collector { + buffer: Vec::new(), + pb, + file: OpenOptions::new() + .write(true) + .append(true) + .create(true) + .open(file_path.as_str()) + .unwrap(), + }); + + if let Some(user) = &repo.details.username { + easy.username(user.as_str())?; + } + if let Some(password) = &repo.details.password { + easy.password(password.as_str())?; + } + if let Some(proxy) = &rc.conf.proxy { + easy.proxy(proxy.as_str())?; + if let Some(username) = &rc.conf.proxy_username { + easy.proxy_username(username.as_str())?; + if let Some(password) = &rc.conf.proxy_password { + easy.proxy_password(password.as_str())?; + } + } + } + easy.timeout(Duration::from_secs(repo.base.timeout))?; + easy.low_speed_time(Duration::from_secs(repo.base.timeout))?; + easy.low_speed_limit(repo.base.minrate)?; + easy.max_recv_speed(repo.base.throttle)?; + easy.ssl_verify_peer(repo.base.sslverify)?; + easy.ssl_verify_host(repo.base.sslverify)?; + if let Some(cert) = &repo.details.ssl_ca_cert { + easy.ssl_cainfo_blob(cert.as_bytes())?; + } + if let Some(cert) = &repo.details.ssl_client_cert { + easy.ssl_cert(cert)?; + } + if let Some(key) = &repo.details.ssl_client_key { + easy.ssl_key(key)?; + } + easy.url(url)?; + easy.follow_location(true)?; + easy.progress(true)?; + match easy.perform() { + Ok(_) => {} + Err(_) => { + for x in 1..10 { + let co = easy.get_mut(); + co.pb.set_message(format!("{}: retrying {}/10", msg, x)); + match easy.perform() { + Ok(_) => { + break; + } + Err(_) => { + continue; + } + } + } + } + } + let collector = easy.get_mut(); + collector.finish()?; + collector.pb.finish_and_clear(); + let status = easy.response_code()?; + if status >= 400 { + bail!("{} when downloading {} Please check repo url or refresh metadata with 'rdnf makecache'",status,url); + } else { + rename(file_path, file)?; + } + + Ok(()) +} +// } + +struct Collector { + buffer: Vec, + pb: ProgressBar, + file: File, +} +impl Collector { + fn finish(&mut self) -> Result<()> { + self.file.write(self.buffer.as_slice())?; + self.buffer.clear(); + Ok(()) + } +} +const LIMIT: usize = 4 * 1024; +impl Handler for Collector { + fn write(&mut self, data: &[u8]) -> Result { + self.buffer.extend_from_slice(data); + if self.buffer.len() >= LIMIT { + match self.file.write_all(self.buffer.as_slice()) { + Ok(_) => {} + Err(_) => { + return Err(curl::easy::WriteError::Pause); + } + }; + self.buffer.clear(); + } + Ok(data.len()) + } + fn progress(&mut self, dltotal: f64, dlnow: f64, _ultotal: f64, _ulnow: f64) -> bool { + if dltotal <= 0.0 { + self.pb.set_length(0); + } + self.pb.set_length(dltotal as u64); + self.pb.set_position(dlnow as u64); + true + } +} +#[derive(Debug, Clone)] +pub enum HashKind { + MD5, + SHA1, + SHA256, + SHA512, + Invalid, +} +impl From<&str> for HashKind { + #[inline] + fn from(kind: &str) -> Self { + match kind.as_bytes() { + b"md5" => Self::MD5, + b"sha1" => Self::SHA1, + b"sha256" => Self::SHA256, + b"sha512" => Self::SHA512, + _ => Self::Invalid, + } + } +} +impl HashKind { + #[inline] + pub fn checksum(self, file: &str, hash: &str) -> Result { + let mut file = File::open(file)?; + let sum = match self { + HashKind::MD5 => { + let mut hasher = Md5::new(); + io::copy(&mut file, &mut hasher)?; + hex::encode(hasher.finalize()) + } + HashKind::SHA1 => { + let mut hasher = Sha1::new(); + io::copy(&mut file, &mut hasher)?; + hex::encode(hasher.finalize()) + } + HashKind::SHA256 => { + let mut hasher = Sha256::new(); + io::copy(&mut file, &mut hasher)?; + hex::encode(hasher.finalize()) + } + + HashKind::SHA512 => { + let mut hasher = Sha512::new(); + io::copy(&mut file, &mut hasher)?; + hex::encode(hasher.finalize()) + } + HashKind::Invalid => { + bail!("Invalid hash algorithm") + } + }; + Ok(sum == hash) + } +} +// #[inline] +// pub fn check_hash(kind:&String,file:&str,hash:&String)->Result{ +// let mut file=File::open(file)?; +// let s=match kind.as_bytes() { +// b"md5" => { +// let mut hasher = Md5::new(); +// io::copy(&mut file,&mut hasher)?; +// hex::encode(hasher.finalize()) +// }, +// b"sha1"=>{ +// let mut hasher=Sha1::new(); +// io::copy(&mut file,&mut hasher)?; +// hex::encode(hasher.finalize()) +// }, +// b"sha256"=>{ +// let mut hasher=Sha256::new(); +// io::copy(&mut file,&mut hasher)?; +// hex::encode(hasher.finalize()) +// } +// b"sha512"=>{ +// let mut hasher=Sha512::new(); +// io::copy(&mut file, &mut hasher)?; +// hex::encode(hasher.finalize()) +// } +// _=>{ +// bail!("invalid hash {}",kind) +// } +// }; +// Ok(s==hash.as_str()) +// } +// #[test] +// fn test(){ +// assert!(check_hash(&"md5".to_string(),"/var/cache/rdnf/rpmfusion-nonfree-updates-77c32c35/tmp/repomd.xml",&"d56148af65634f42c1a2c9bd6a0597be".to_string()).unwrap()); +// } diff --git a/rdnf/src/sub_command/search.rs b/rdnf/src/sub_command/search.rs new file mode 100644 index 00000000..4902686e --- /dev/null +++ b/rdnf/src/sub_command/search.rs @@ -0,0 +1,144 @@ +use std::ffi::{CStr, CString}; + +use anyhow::{bail, Result}; +use console::style; +use solv_sys::ffi::{ + dataiterator_free, dataiterator_set_keyname, dataiterator_step, queue_free, queue_insertn, + selection_solvables, solv_knownid_SOLVABLE_ARCH, solv_knownid_SOLVABLE_DESCRIPTION, + solv_knownid_SOLVABLE_NAME, solv_knownid_SOLVABLE_SUMMARY, solvable_lookup_str, Dataiterator, + Queue, SEARCH_NOCASE, SEARCH_SUBSTRING, SOLVER_SOLVABLE, +}; + +use crate::{ + c_lib::{ + create_dataiterator_empty, dataiterator_init_simple, dataiterator_set_search_simple, + get_queue_element_value, pool_id2solvable, queue_empty, queue_push2, + }, + errors::{ERROR_RDNF_NO_DATA, ERROR_RDNF_NO_MATCH}, + solv::rdnf_query::{init_queue, SolvQuery}, + Rdnf, +}; + +impl Rdnf { + pub fn search_pkg(&mut self, pkgs: Vec) -> Result<()> { + self.make_cache()?; + let pkg_infos = SolvQuery::default(self.rc.sack.clone()) + .apply_search(pkgs)? + .get_query_result()?; + let offset = pkg_infos + .iter() + .max_by_key(|x| x.pkg_name.len()) + .unwrap() + .pkg_name + .len() + + 5; + let (_, width) = self.rc.term.size(); + for pkg in pkg_infos { + self.rc + .term + .write_line(&format!("{}", style(pkg.pkg_name).green()))?; + if offset < width as usize { + self.rc.term.move_cursor_up(1)?; + } + self.rc.term.move_cursor_right(offset)?; + self.rc + .term + .write_line(&format!("{}", style(pkg.pkg_summary).yellow()))?; + } + Ok(()) + } +} +impl SolvQuery { + pub fn apply_search(mut self, pkgs: Vec) -> Result { + let mut queue_sel = init_queue(); + let mut queue_result = init_queue(); + let mut di = create_dataiterator_empty(); + let pool = self.sack.pool; + unsafe { + let di_ptr = &mut di as *mut Dataiterator; + let queue_sel_ptr = &mut queue_sel as *mut Queue; + let queue_result_ptr = &mut queue_result as *mut Queue; + + for pkg in pkgs { + let pkg_ptr = CString::new(pkg.as_str()).unwrap(); + queue_empty(queue_sel_ptr); + queue_empty(queue_result_ptr); + dataiterator_init_simple( + di_ptr, + pool, + pkg_ptr.as_ptr(), + (SEARCH_SUBSTRING | SEARCH_NOCASE) as i32, + ); + + dataiterator_set_keyname(di_ptr, solv_knownid_SOLVABLE_NAME as i32); + dataiterator_set_search_simple(di_ptr); + while dataiterator_step(di_ptr) != 0 { + queue_push2(queue_sel_ptr, SOLVER_SOLVABLE as i32, di.solvid); + } + + dataiterator_set_keyname(di_ptr, solv_knownid_SOLVABLE_SUMMARY as i32); + dataiterator_set_search_simple(di_ptr); + while dataiterator_step(di_ptr) != 0 { + queue_push2(queue_sel_ptr, SOLVER_SOLVABLE as i32, di.solvid); + } + + dataiterator_set_keyname(di_ptr, solv_knownid_SOLVABLE_DESCRIPTION as i32); + dataiterator_set_search_simple(di_ptr); + while dataiterator_step(di_ptr) != 0 { + queue_push2(queue_sel_ptr, SOLVER_SOLVABLE as i32, di.solvid); + } + dataiterator_free(di_ptr); + + selection_solvables(pool, queue_sel_ptr, queue_result_ptr); + let q = &mut self.queue_result as *mut Queue; + queue_insertn( + q, + (*q).count, + (*queue_result_ptr).count, + (*queue_result_ptr).elements, + ); + } + queue_free(queue_sel_ptr); + queue_free(queue_result_ptr); + } + Ok(self) + } + pub fn get_query_result(self) -> Result> { + if self.queue_result.count == 0 { + bail!(ERROR_RDNF_NO_MATCH); + } + let mut pkg_infos = Vec::new(); + for index in 0..self.queue_result.count { + let pkg_id = get_queue_element_value(&self.queue_result as *const Queue, index as u32); + + let solv = pool_id2solvable(self.sack.pool, pkg_id); + let pkg_name = self.sack.solv_get_pkg_name_by_id(pkg_id)?; + let pkg_summary = unsafe { + let temp_summary_ptr = + solvable_lookup_str(solv, solv_knownid_SOLVABLE_SUMMARY as i32); + if temp_summary_ptr.is_null() { + bail!(ERROR_RDNF_NO_DATA); + } + CStr::from_ptr(temp_summary_ptr).to_str()?.to_string() + }; + let pkg_arch = unsafe { + CStr::from_ptr(solvable_lookup_str(solv, solv_knownid_SOLVABLE_ARCH as i32)) + .to_str()? + }; + let pkg_name = pkg_name + "." + pkg_arch; + let pkg_info = SearchPkgInfo { + pkg_id, + pkg_name, + pkg_summary, + }; + pkg_infos.push(pkg_info); + } + Ok(pkg_infos) + } +} +#[derive(Debug, Clone)] +pub struct SearchPkgInfo { + pub pkg_id: i32, + pub pkg_name: String, + pub pkg_summary: String, +} diff --git a/rdnf/src/sub_command/update.rs b/rdnf/src/sub_command/update.rs new file mode 100644 index 00000000..d47b074a --- /dev/null +++ b/rdnf/src/sub_command/update.rs @@ -0,0 +1,296 @@ +use std::{ + ffi::{CStr, CString}, +}; + +use anyhow::{bail, Result}; +use chrono::NaiveDateTime; +use libc::{atof}; +use solv_sys::ffi::{ + dataiterator_free, dataiterator_init, dataiterator_prepend_keyname, dataiterator_setpos, + dataiterator_setpos_parent, dataiterator_skip_solvable, dataiterator_step, pool_evrcmp, + pool_id2str, pool_lookup_id, pool_lookup_num, pool_lookup_str, pool_lookup_void, + solv_knownid_SOLVABLE_BUILDTIME, solv_knownid_SOLVABLE_DESCRIPTION, solv_knownid_SOLVABLE_NAME, + solv_knownid_SOLVABLE_PATCHCATEGORY, solv_knownid_UPDATE_COLLECTION, + solv_knownid_UPDATE_COLLECTION_ARCH, solv_knownid_UPDATE_COLLECTION_EVR, + solv_knownid_UPDATE_COLLECTION_FILENAME, solv_knownid_UPDATE_COLLECTION_NAME, + solv_knownid_UPDATE_REBOOT, solv_knownid_UPDATE_SEVERITY, Repo, EVRCMP_COMPARE, SEARCH_STRING, + SOLVID_POS, +}; + +use crate::{ + c_lib::{create_dataiterator_empty, pool_id2solvable, queue_push}, + errors::ERROR_RDNF_INVALID_PARAMETER, + solv::{rdnf_query::init_queue, sack::Solvsack, SolvPackageList}, + utils::c_str_ptr_to_rust_string, + Rdnf, +}; +pub enum UpdateInfoKind { + Unknown, + Security, + Bugfix, + Enhancement, +} +pub struct UpdateInfo { + kind: UpdateInfoKind, + pkg_id: Option, + pkg_date: Option, + pkg_desc: Option, + reboot_required: bool, + _refers: Vec, + pkgs: Vec, +} +impl UpdateInfo { + pub fn default() -> Self { + UpdateInfo { + kind: UpdateInfoKind::Unknown, + pkg_id: None, + pkg_date: None, + pkg_desc: None, + reboot_required: false, + _refers: Vec::::new(), + pkgs: Vec::::new(), + } + } +} +pub struct UpdateInfoRef { + pub pkg_id: Option, + pub pkg_link: Option, + pub pkg_title: Option, + pub pkg_type: Option, +} +pub struct UpdateInfoPkg { + pkg_name: Option, + pkg_file_name: Option, + pkg_evr: Option, + pkg_arch: Option, +} +impl UpdateInfoPkg { + pub fn default() -> Self { + UpdateInfoPkg { + pkg_name: None, + pkg_file_name: None, + pkg_evr: None, + pkg_arch: None, + } + } +} +impl Rdnf { + pub fn update_info(&self, pkg_name: Option>) -> Result> { + // self.make_cache()?; + let installed_pkg_list = match pkg_name { + Some(s) => self.rc.sack.solv_find_installed_pkg_by_multiple_names(s)?, + None => self.rc.sack.solv_find_all_installed()?, + }; + let security = self.rc.cli.security; + let sec_severity = self.rc.cli.sec_severity.clone(); + let reboot_required = self.rc.cli.reboot_required; + let count = installed_pkg_list.get_size(); + let mut infos=Vec::new(); + for index in 0..count { + let id = installed_pkg_list.get_pkg_id(index); + match self.rc.sack.solv_get_update_advisories(id) { + Err(_) => { + continue; + } + Ok(update_adv_pkg_list) => { + let ucount = update_adv_pkg_list.get_size(); + for uadv in 0..ucount { + let adv_id = update_adv_pkg_list.get_pkg_id(uadv); + let info=self.rc.sack.populate_updateinfo_of_one_advisory(adv_id, security, &sec_severity, reboot_required)?; + if info.is_some() { + infos.push(info.unwrap()) + } + + } + } + }; + } + if infos.len() >0{ + self.rc.term.write_line(&format!("{} updates.",infos.len()))?; + } + Ok(infos) + } + pub fn get_update_pkgs(&self,) -> Result> { + let update_infos=self.update_info(None)?; + let count=update_infos.len(); + let mut pkgs=Vec::new(); + if count>0{ + for update_info in update_infos { + for update_info_pkg in update_info.pkgs { + if update_info_pkg.pkg_name.is_some() { + pkgs.push(update_info_pkg.pkg_name.unwrap()) + } + } + } + } + Ok(pkgs) + } +} +impl Solvsack { + pub fn solv_get_update_advisories(&self, id: i32) -> Result { + let mut queue_adv = init_queue(); + let solvable = pool_id2solvable(self.pool, id); + if solvable.is_null() { + bail!(ERROR_RDNF_INVALID_PARAMETER); + }; + let pkg_name_ptr = unsafe { pool_id2str(self.pool, (*solvable).name) }; + let mut di = create_dataiterator_empty(); + unsafe { + dataiterator_init( + &mut di, + self.pool, + 0 as *mut Repo, + 0, + solv_knownid_UPDATE_COLLECTION_NAME as i32, + pkg_name_ptr, + SEARCH_STRING as i32, + ); + dataiterator_prepend_keyname(&mut di, solv_knownid_UPDATE_COLLECTION as i32); + while dataiterator_step(&mut di) != 0 { + dataiterator_setpos_parent(&mut di); + let arch = pool_lookup_id( + self.pool, + SOLVID_POS, + solv_knownid_UPDATE_COLLECTION_ARCH as i32, + ); + if arch != (*solvable).arch { + continue; + } + let evr = pool_lookup_id( + self.pool, + SOLVID_POS, + solv_knownid_UPDATE_COLLECTION_EVR as i32, + ); + if evr == 0 { + continue; + } + let cmp_result = + pool_evrcmp(self.pool, evr, (*solvable).evr, EVRCMP_COMPARE as i32); + if cmp_result > 0 { + queue_push(&mut queue_adv, di.solvid); + dataiterator_skip_solvable(&mut di); + } + } + }; + unsafe { dataiterator_free(&mut di) }; + SolvPackageList::queue_to_pkg_list(&mut queue_adv) + } + pub fn populate_updateinfo_of_one_advisory( + &self, + adv_id: i32, + security: bool, + sec_severity: &Option, + reboot_required: bool, + ) -> Result> { + let psz_type_ptr = unsafe { + pool_lookup_str( + self.pool, + adv_id, + solv_knownid_SOLVABLE_PATCHCATEGORY as i32, + ) + }; + let temp_ptr = + unsafe { pool_lookup_str(self.pool, adv_id, solv_knownid_UPDATE_SEVERITY as i32) }; + let reboot = + unsafe { pool_lookup_void(self.pool, adv_id, solv_knownid_UPDATE_REBOOT as i32) } == 1; + let mut keep_entry = true; + if security { + if !psz_type_ptr.is_null() { + if unsafe { CStr::from_ptr(psz_type_ptr).to_str()? } == "security" { + keep_entry = false; + } + } + } else if sec_severity.is_some() { + let severity = CString::new(sec_severity.as_ref().unwrap().as_str())?; + if temp_ptr.is_null() || unsafe { atof(severity.as_ptr()) > atof(temp_ptr) } { + keep_entry = false; + } + } + if reboot_required { + if reboot { + keep_entry = false; + } + } + if keep_entry { + let mut update_info = UpdateInfo::default(); + update_info.kind = if psz_type_ptr.is_null() { + UpdateInfoKind::Unknown + } else { + let psz_type = unsafe { CStr::from_ptr(psz_type_ptr).to_str()? }; + match psz_type { + "bugfix" => UpdateInfoKind::Bugfix, + "enhancement" => UpdateInfoKind::Enhancement, + "security" => UpdateInfoKind::Security, + _ => UpdateInfoKind::Unknown, + } + }; + update_info.reboot_required = reboot; + update_info.pkg_id=c_str_ptr_to_rust_string(unsafe { + pool_lookup_str(self.pool, adv_id, solv_knownid_SOLVABLE_NAME as i32) + }); + update_info.pkg_desc=c_str_ptr_to_rust_string(unsafe { + pool_lookup_str(self.pool, adv_id, solv_knownid_SOLVABLE_DESCRIPTION as i32) + }); + let updated = unsafe { + pool_lookup_num(self.pool, adv_id, solv_knownid_SOLVABLE_BUILDTIME as i32, 0) + }; + if updated > 0 { + update_info.pkg_date = + Some(NaiveDateTime::from_timestamp_opt(updated as i64, 0).unwrap()); + } + update_info.pkgs=self.get_update_info_pkgs(adv_id)?; + return Ok(Some(update_info)); + } + Ok(None) + } + pub fn get_update_info_pkgs(&self, id: i32) -> Result> { + let mut di = create_dataiterator_empty(); + unsafe { + dataiterator_init( + &mut di, + self.pool, + 0 as *mut Repo, + id, + solv_knownid_UPDATE_COLLECTION as i32, + 0 as *const i8, + 0, + ) + }; + let mut update_pkgs = Vec::new(); + while unsafe { dataiterator_step(&mut di) } != 0 { + unsafe { dataiterator_setpos(&mut di) }; + let mut update_pkg = UpdateInfoPkg::default(); + update_pkg.pkg_name = c_str_ptr_to_rust_string(unsafe { + pool_lookup_str( + self.pool, + SOLVID_POS as i32, + solv_knownid_UPDATE_COLLECTION_NAME as i32, + ) + }); + update_pkg.pkg_evr = c_str_ptr_to_rust_string(unsafe { + pool_lookup_str( + self.pool, + SOLVID_POS as i32, + solv_knownid_UPDATE_COLLECTION_EVR as i32, + ) + }); + update_pkg.pkg_arch = c_str_ptr_to_rust_string(unsafe { + pool_lookup_str( + self.pool, + SOLVID_POS as i32, + solv_knownid_UPDATE_COLLECTION_ARCH as i32, + ) + }); + update_pkg.pkg_file_name = c_str_ptr_to_rust_string(unsafe { + pool_lookup_str( + self.pool, + SOLVID_POS as i32, + solv_knownid_UPDATE_COLLECTION_FILENAME as i32, + ) + }); + update_pkgs.push(update_pkg); + } + unsafe{dataiterator_free(&mut di)}; + Ok(update_pkgs) + } +} diff --git a/rdnf/src/utils.rs b/rdnf/src/utils.rs new file mode 100644 index 00000000..e52b7953 --- /dev/null +++ b/rdnf/src/utils.rs @@ -0,0 +1,76 @@ +use std::ffi::CStr; + +use anyhow::{bail, Result}; +use libc::geteuid; + +use crate::{ + default::RDNF_INSTANCE_LOCK_FILE, + lock::{flock_acquire, flock_release}, +}; + +use super::lock::{flock_new, RdnfFlockMode}; + +pub fn check_root() -> Result<()> { + if unsafe { geteuid() } != 0 { + bail!("root permission is required for this operation"); + } + Ok(()) +} + +pub fn is_already_running() -> Result<()> { + if unsafe { geteuid() } == 0 { + let mut lock = flock_new(RDNF_INSTANCE_LOCK_FILE, "rdnf_instance")?; + if !flock_acquire(&mut lock, RdnfFlockMode::WriteRead)? { + match lock.openmode { + RdnfFlockMode::WriteRead => { + println!("waiting for {} lock on {}", lock.descr, lock.path); + if !flock_acquire(&mut lock, RdnfFlockMode::Wait)? { + flock_release(&mut lock); + bail!("can't create {} lock on {}", lock.descr, lock.path); + } + } + _ => { + flock_release(&mut lock); + bail!("Failed to acquire rdnf_instance lock") + } + } + } + } + Ok(()) +} +pub fn check_dir(path: &str) -> Result { + match std::fs::read_dir(path) { + Ok(c) => { + return Ok(c.count() > 0); + } + Err(_) => { + bail!("Dir {} don't exist", path) + } + } +} +pub fn format_size(size: u64) -> String { + let mut dsize = size as f32; + for i in ["b", "k", "M", "G"] { + if dsize >= 1024.0 { + dsize /= 1024.0; + } else { + return format!("{:.2}{}", dsize, i); + } + } + format!("{:.2}T", dsize) +} +#[inline] +pub fn c_str_ptr_to_rust_string(ptr:*const i8)->Option{ + if ptr.is_null(){ + None + }else{ + unsafe{ + Some(CStr::from_ptr(ptr).to_str().unwrap().to_string()) + } + } +} +#[cfg(test)] +mod tests { + #[test] + pub fn test() {} +} -- Gitee