From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) by lore.proxmox.com (Postfix) with ESMTPS id DB0031FF2CF for ; Tue, 9 Jul 2024 08:19:42 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 36CE318817; Tue, 9 Jul 2024 08:20:05 +0200 (CEST) From: Wolfgang Bumiller To: pbs-devel@lists.proxmox.com Date: Tue, 9 Jul 2024 08:19:27 +0200 Message-Id: <20240709061927.52699-1-w.bumiller@proxmox.com> X-Mailer: git-send-email 2.39.2 MIME-Version: 1.0 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.036 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment PROLO_LEO1 0.1 Meta Catches all Leo drug variations so far SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record Subject: [pbs-devel] applied: [PATCH backup] use new apt/apt-api-types crate X-BeenThere: pbs-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Proxmox Backup Server development discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: Proxmox Backup Server development discussion Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Errors-To: pbs-devel-bounces@lists.proxmox.com Sender: "pbs-devel" From: Dietmar Maurer --- Cargo.toml | 6 +- pbs-api-types/Cargo.toml | 1 + pbs-api-types/src/lib.rs | 35 +--- pbs-buildcfg/src/lib.rs | 2 + src/api2/node/apt.rs | 402 +++++---------------------------------- src/bin/pbs2to3.rs | 26 +-- src/tools/apt.rs | 293 ---------------------------- src/tools/mod.rs | 1 - 8 files changed, 72 insertions(+), 694 deletions(-) delete mode 100644 src/tools/apt.rs diff --git a/Cargo.toml b/Cargo.toml index 17aeeb21..8120526a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,7 +53,9 @@ path = "src/lib.rs" [workspace.dependencies] # proxmox workspace -proxmox-apt = "0.10.5" +proxmox-config-digest = "0.1.0" +proxmox-apt = { version = "0.11", features = [ "cache" ] } +proxmox-apt-api-types = "1.0" proxmox-async = "0.4" proxmox-auth-api = "0.4" proxmox-borrow = "1" @@ -203,9 +205,11 @@ zstd.workspace = true # proxmox workspace proxmox-apt.workspace = true +proxmox-apt-api-types.workspace = true proxmox-async.workspace = true proxmox-auth-api = { workspace = true, features = [ "api", "pam-authenticator" ] } proxmox-compression.workspace = true +proxmox-config-digest.workspace = true proxmox-http = { workspace = true, features = [ "client-trait", "proxmox-async", "rate-limited-stream" ] } # pbs-client doesn't use these proxmox-human-byte.workspace = true proxmox-io.workspace = true diff --git a/pbs-api-types/Cargo.toml b/pbs-api-types/Cargo.toml index 94ab583b..808ff514 100644 --- a/pbs-api-types/Cargo.toml +++ b/pbs-api-types/Cargo.toml @@ -16,6 +16,7 @@ serde.workspace = true serde_plain.workspace = true proxmox-auth-api = { workspace = true, features = [ "api-types" ] } +proxmox-apt-api-types.workspace = true proxmox-human-byte.workspace = true proxmox-lang.workspace=true proxmox-schema = { workspace = true, features = [ "api-macro" ] } diff --git a/pbs-api-types/src/lib.rs b/pbs-api-types/src/lib.rs index a3ad185b..40bcd8f1 100644 --- a/pbs-api-types/src/lib.rs +++ b/pbs-api-types/src/lib.rs @@ -52,6 +52,13 @@ pub use proxmox_schema::api_types::{SYSTEMD_DATETIME_FORMAT, TIME_ZONE_SCHEMA}; use proxmox_schema::api_types::{DNS_NAME_STR, IPRE_BRACKET_STR}; +// re-export APT API types +pub use proxmox_apt_api_types::{ + APTChangeRepositoryOptions, APTGetChangelogOptions, APTRepositoriesResult, APTRepositoryFile, + APTRepositoryFileError, APTRepositoryHandle, APTRepositoryInfo, APTStandardRepository, + APTUpdateInfo, APTUpdateOptions, +}; + #[rustfmt::skip] pub const BACKUP_ID_RE: &str = r"[A-Za-z0-9_][A-Za-z0-9._\-]*"; @@ -249,34 +256,6 @@ pub const PASSWORD_HINT_SCHEMA: Schema = StringSchema::new("Password hint.") .max_length(64) .schema(); -#[api()] -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "PascalCase")] -/// Describes a package for which an update is available. -pub struct APTUpdateInfo { - /// Package name - pub package: String, - /// Package title - pub title: String, - /// Package architecture - pub arch: String, - /// Human readable package description - pub description: String, - /// New version to be updated to - pub version: String, - /// Old version currently installed - pub old_version: String, - /// Package origin - pub origin: String, - /// Package priority in human-readable form - pub priority: String, - /// Package section - pub section: String, - /// Custom extra field for additional package information - #[serde(skip_serializing_if = "Option::is_none")] - pub extra_info: Option, -} - #[api()] #[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "lowercase")] diff --git a/pbs-buildcfg/src/lib.rs b/pbs-buildcfg/src/lib.rs index 8ce2f7e5..3d087015 100644 --- a/pbs-buildcfg/src/lib.rs +++ b/pbs-buildcfg/src/lib.rs @@ -98,6 +98,8 @@ pub const PROXMOX_BACKUP_KERNEL_FN: &str = pub const PROXMOX_BACKUP_SUBSCRIPTION_FN: &str = configdir!("/subscription"); +pub const APT_PKG_STATE_FN: &str = concat!(PROXMOX_BACKUP_STATE_DIR_M!(), "/pkg-state.json"); + /// Prepend configuration directory to a file name /// /// This is a simply way to get the full path for configuration files. diff --git a/src/api2/node/apt.rs b/src/api2/node/apt.rs index 0ae353d5..7b604cc0 100644 --- a/src/api2/node/apt.rs +++ b/src/api2/node/apt.rs @@ -1,26 +1,21 @@ -use anyhow::{bail, format_err, Error}; -use serde_json::{json, Value}; -use std::os::unix::prelude::OsStrExt; +use anyhow::{bail, Error}; +use proxmox_config_digest::ConfigDigest; use proxmox_router::{ list_subdirs_api_method, Permission, Router, RpcEnvironment, RpcEnvironmentType, SubdirMap, }; use proxmox_schema::api; use proxmox_sys::fs::{replace_file, CreateOptions}; -use proxmox_apt::repositories::{ - APTRepositoryFile, APTRepositoryFileError, APTRepositoryHandle, APTRepositoryInfo, - APTStandardRepository, +use proxmox_apt_api_types::{ + APTChangeRepositoryOptions, APTGetChangelogOptions, APTRepositoriesResult, APTRepositoryHandle, + APTUpdateInfo, APTUpdateOptions, }; use proxmox_http::ProxyConfig; -use pbs_api_types::{ - APTUpdateInfo, NODE_SCHEMA, PRIV_SYS_AUDIT, PRIV_SYS_MODIFY, PROXMOX_CONFIG_DIGEST_SCHEMA, - UPID_SCHEMA, -}; +use pbs_api_types::{NODE_SCHEMA, PRIV_SYS_AUDIT, PRIV_SYS_MODIFY, UPID_SCHEMA}; use crate::config::node; -use crate::tools::apt; use proxmox_rest_server::WorkerTask; #[api( @@ -44,16 +39,8 @@ use proxmox_rest_server::WorkerTask; }, )] /// List available APT updates -fn apt_update_available(_param: Value) -> Result { - if let Ok(false) = apt::pkg_cache_expired() { - if let Ok(Some(cache)) = apt::read_pkg_state() { - return Ok(json!(cache.package_status)); - } - } - - let cache = apt::update_cache()?; - - Ok(json!(cache.package_status)) +pub fn apt_update_available() -> Result, Error> { + proxmox_apt::list_available_apt_update(pbs_buildcfg::APT_PKG_STATE_FN) } pub fn update_apt_proxy_config(proxy_config: Option<&ProxyConfig>) -> Result<(), Error> { @@ -83,45 +70,6 @@ fn read_and_update_proxy_config() -> Result, Error> { Ok(proxy_config) } -fn do_apt_update(worker: &WorkerTask, quiet: bool) -> Result<(), Error> { - if !quiet { - worker.log_message("starting apt-get update") - } - - read_and_update_proxy_config()?; - - let mut command = std::process::Command::new("apt-get"); - command.arg("update"); - - // apt "errors" quite easily, and run_command is a bit rigid, so handle this inline for now. - let output = command - .output() - .map_err(|err| format_err!("failed to execute {:?} - {}", command, err))?; - - if !quiet { - worker.log_message(String::from_utf8(output.stdout)?); - } - - // TODO: improve run_command to allow outputting both, stderr and stdout - if !output.status.success() { - if output.status.code().is_some() { - let msg = String::from_utf8(output.stderr) - .map(|m| { - if m.is_empty() { - String::from("no error message") - } else { - m - } - }) - .unwrap_or_else(|_| String::from("non utf8 error message (suppressed)")); - worker.log_warning(msg); - } else { - bail!("terminated by signal"); - } - } - Ok(()) -} - #[api( protected: true, input: { @@ -129,19 +77,10 @@ fn do_apt_update(worker: &WorkerTask, quiet: bool) -> Result<(), Error> { node: { schema: NODE_SCHEMA, }, - notify: { - type: bool, - description: r#"Send notification mail about new package updates available to the - email address configured for 'root@pam')."#, - default: false, - optional: true, - }, - quiet: { - description: "Only produces output suitable for logging, omitting progress indicators.", - type: bool, - default: false, - optional: true, - }, + options: { + type: APTUpdateOptions, + flatten: true, + } }, }, returns: { @@ -153,40 +92,22 @@ fn do_apt_update(worker: &WorkerTask, quiet: bool) -> Result<(), Error> { )] /// Update the APT database pub fn apt_update_database( - notify: bool, - quiet: bool, + options: APTUpdateOptions, rpcenv: &mut dyn RpcEnvironment, ) -> Result { let auth_id = rpcenv.get_auth_id().unwrap(); let to_stdout = rpcenv.env_type() == RpcEnvironmentType::CLI; - let upid_str = WorkerTask::new_thread("aptupdate", None, auth_id, to_stdout, move |worker| { - do_apt_update(&worker, quiet)?; - - let mut cache = apt::update_cache()?; - - if notify { - let mut notified = cache.notified.unwrap_or_default(); - let mut to_notify: Vec<&APTUpdateInfo> = Vec::new(); - - for pkg in &cache.package_status { - match notified.insert(pkg.package.to_owned(), pkg.version.to_owned()) { - Some(notified_version) => { - if notified_version != pkg.version { - to_notify.push(pkg); - } - } - None => to_notify.push(pkg), - } - } - if !to_notify.is_empty() { - to_notify.sort_unstable_by_key(|k| &k.package); - crate::server::send_updates_available(&to_notify)?; - } - cache.notified = Some(notified); - apt::write_pkg_cache(&cache)?; - } - + let upid_str = WorkerTask::new_thread("aptupdate", None, auth_id, to_stdout, move |_worker| { + read_and_update_proxy_config()?; + proxmox_apt::update_database( + pbs_buildcfg::APT_PKG_STATE_FN, + &options, + |updates: &[&APTUpdateInfo]| { + crate::server::send_updates_available(updates)?; + Ok(()) + }, + )?; Ok(()) })?; @@ -200,14 +121,9 @@ pub fn apt_update_database( node: { schema: NODE_SCHEMA, }, - name: { - description: "Package name to get changelog of.", - type: String, - }, - version: { - description: "Package version to get changelog of. Omit to use candidate version.", - type: String, - optional: true, + options: { + type: APTGetChangelogOptions, + flatten: true, }, }, }, @@ -219,17 +135,8 @@ pub fn apt_update_database( }, )] /// Retrieve the changelog of the specified package. -fn apt_get_changelog(name: String, version: Option) -> Result { - let mut command = std::process::Command::new("apt-get"); - command.arg("changelog"); - command.arg("-qq"); // don't display download progress - if let Some(ver) = version { - command.arg(format!("{name}={ver}")); - } else { - command.arg(name); - } - let output = proxmox_sys::command::run_command(command, None)?; - Ok(json!(output)) +fn apt_get_changelog(options: APTGetChangelogOptions) -> Result { + proxmox_apt::get_changelog(&options) } #[api( @@ -256,10 +163,8 @@ pub fn get_versions() -> Result, Error> { const PACKAGES: &[&str] = &[ "ifupdown2", "libjs-extjs", - "proxmox-backup", "proxmox-backup-docs", "proxmox-backup-client", - "proxmox-backup-server", "proxmox-mail-forward", "proxmox-mini-journalreader", "proxmox-offline-mirror-helper", @@ -269,96 +174,16 @@ pub fn get_versions() -> Result, Error> { "zfsutils-linux", ]; - fn unknown_package(package: String, extra_info: Option) -> APTUpdateInfo { - APTUpdateInfo { - package, - title: "unknown".into(), - arch: "unknown".into(), - description: "unknown".into(), - version: "unknown".into(), - old_version: "unknown".into(), - origin: "unknown".into(), - priority: "unknown".into(), - section: "unknown".into(), - extra_info, - } - } - - let is_kernel = - |name: &str| name.starts_with("pve-kernel-") || name.starts_with("proxmox-kernel"); - - let mut packages: Vec = Vec::new(); - let pbs_packages = apt::list_installed_apt_packages( - |filter| { - filter.installed_version == Some(filter.active_version) - && (is_kernel(filter.package) || PACKAGES.contains(&filter.package)) - }, - None, - ); - - let running_kernel = format!( - "running kernel: {}", - std::str::from_utf8(nix::sys::utsname::uname()?.release().as_bytes())?.to_owned() - ); - if let Some(proxmox_backup) = pbs_packages - .iter() - .find(|pkg| pkg.package == "proxmox-backup") - { - let mut proxmox_backup = proxmox_backup.clone(); - proxmox_backup.extra_info = Some(running_kernel); - packages.push(proxmox_backup); - } else { - packages.push(unknown_package( - "proxmox-backup".into(), - Some(running_kernel), - )); - } - let version = pbs_buildcfg::PROXMOX_PKG_VERSION; let release = pbs_buildcfg::PROXMOX_PKG_RELEASE; - let daemon_version_info = Some(format!("running version: {}.{}", version, release)); - if let Some(pkg) = pbs_packages - .iter() - .find(|pkg| pkg.package == "proxmox-backup-server") - { - let mut pkg = pkg.clone(); - pkg.extra_info = daemon_version_info; - packages.push(pkg); - } else { - packages.push(unknown_package( - "proxmox-backup".into(), - daemon_version_info, - )); - } - - let mut kernel_pkgs: Vec = pbs_packages - .iter() - .filter(|pkg| is_kernel(&pkg.package)) - .cloned() - .collect(); - // make sure the cache mutex gets dropped before the next call to list_installed_apt_packages - { - let cache = apt_pkg_native::Cache::get_singleton(); - kernel_pkgs.sort_by(|left, right| { - cache - .compare_versions(&left.old_version, &right.old_version) - .reverse() - }); - } - packages.append(&mut kernel_pkgs); + let running_daemon_version = format!("running version: {version}.{release}"); - // add entry for all packages we're interested in, even if not installed - for pkg in PACKAGES.iter() { - if pkg == &"proxmox-backup" || pkg == &"proxmox-backup-server" { - continue; - } - match pbs_packages.iter().find(|item| &item.package == pkg) { - Some(apt_pkg) => packages.push(apt_pkg.to_owned()), - None => packages.push(unknown_package(pkg.to_string(), None)), - } - } - - Ok(packages) + proxmox_apt::get_package_versions( + "proxmox-backup", + "proxmox-backup-server", + &running_daemon_version, + &PACKAGES, + ) } #[api( @@ -370,61 +195,15 @@ pub fn get_versions() -> Result, Error> { }, }, returns: { - type: Object, - description: "Result from parsing the APT repository files in /etc/apt/.", - properties: { - files: { - description: "List of parsed repository files.", - type: Array, - items: { - type: APTRepositoryFile, - }, - }, - errors: { - description: "List of problematic files.", - type: Array, - items: { - type: APTRepositoryFileError, - }, - }, - digest: { - schema: PROXMOX_CONFIG_DIGEST_SCHEMA, - }, - infos: { - description: "List of additional information/warnings about the repositories.", - items: { - type: APTRepositoryInfo, - }, - }, - "standard-repos": { - description: "List of standard repositories and their configuration status.", - items: { - type: APTStandardRepository, - }, - }, - }, + type: APTRepositoriesResult, }, access: { permission: &Permission::Privilege(&[], PRIV_SYS_AUDIT, false), }, )] /// Get APT repository information. -pub fn get_repositories() -> Result { - let (files, errors, digest) = proxmox_apt::repositories::repositories()?; - let digest = hex::encode(digest); - - let suite = proxmox_apt::repositories::get_current_release_codename()?; - - let infos = proxmox_apt::repositories::check_repositories(&files, suite); - let standard_repos = proxmox_apt::repositories::standard_repositories(&files, "pbs", suite); - - Ok(json!({ - "files": files, - "errors": errors, - "digest": digest, - "infos": infos, - "standard-repos": standard_repos, - })) +pub fn get_repositories() -> Result { + proxmox_apt::list_repositories("pbs") } #[api( @@ -437,7 +216,7 @@ pub fn get_repositories() -> Result { type: APTRepositoryHandle, }, digest: { - schema: PROXMOX_CONFIG_DIGEST_SCHEMA, + type: ConfigDigest, optional: true, }, }, @@ -451,61 +230,11 @@ pub fn get_repositories() -> Result { /// If the repository is already configured, it will be set to enabled. /// /// The `digest` parameter asserts that the configuration has not been modified. -pub fn add_repository(handle: APTRepositoryHandle, digest: Option) -> Result<(), Error> { - let (mut files, errors, current_digest) = proxmox_apt::repositories::repositories()?; - - let suite = proxmox_apt::repositories::get_current_release_codename()?; - - if let Some(expected_digest) = digest { - let current_digest = hex::encode(current_digest); - crate::tools::assert_if_modified(&expected_digest, ¤t_digest)?; - } - - // check if it's already configured first - for file in files.iter_mut() { - for repo in file.repositories.iter_mut() { - if repo.is_referenced_repository(handle, "pbs", &suite.to_string()) { - if repo.enabled { - return Ok(()); - } - - repo.set_enabled(true); - file.write()?; - - return Ok(()); - } - } - } - - let (repo, path) = proxmox_apt::repositories::get_standard_repository(handle, "pbs", suite); - - if let Some(error) = errors.iter().find(|error| error.path == path) { - bail!( - "unable to parse existing file {} - {}", - error.path, - error.error, - ); - } - - if let Some(file) = files - .iter_mut() - .find(|file| file.path.as_ref() == Some(&path)) - { - file.repositories.push(repo); - - file.write()?; - } else { - let mut file = match APTRepositoryFile::new(&path)? { - Some(file) => file, - None => bail!("invalid path - {}", path), - }; - - file.repositories.push(repo); - - file.write()?; - } - - Ok(()) +pub fn add_repository( + handle: APTRepositoryHandle, + digest: Option, +) -> Result<(), Error> { + proxmox_apt::add_repository_handle("pbs", handle, digest) } #[api( @@ -522,13 +251,12 @@ pub fn add_repository(handle: APTRepositoryHandle, digest: Option) -> Re description: "Index within the file (starting from 0).", type: usize, }, - enabled: { - description: "Whether the repository should be enabled or not.", - type: bool, - optional: true, + options: { + type: APTChangeRepositoryOptions, + flatten: true, }, digest: { - schema: PROXMOX_CONFIG_DIGEST_SCHEMA, + type: ConfigDigest, optional: true, }, }, @@ -544,38 +272,10 @@ pub fn add_repository(handle: APTRepositoryHandle, digest: Option) -> Re pub fn change_repository( path: String, index: usize, - enabled: Option, - digest: Option, + options: APTChangeRepositoryOptions, + digest: Option, ) -> Result<(), Error> { - let (mut files, errors, current_digest) = proxmox_apt::repositories::repositories()?; - - if let Some(expected_digest) = digest { - let current_digest = hex::encode(current_digest); - crate::tools::assert_if_modified(&expected_digest, ¤t_digest)?; - } - - if let Some(error) = errors.iter().find(|error| error.path == path) { - bail!("unable to parse file {} - {}", error.path, error.error); - } - - if let Some(file) = files - .iter_mut() - .find(|file| file.path.as_ref() == Some(&path)) - { - if let Some(repo) = file.repositories.get_mut(index) { - if let Some(enabled) = enabled { - repo.set_enabled(enabled); - } - - file.write()?; - } else { - bail!("invalid index - {}", index); - } - } else { - bail!("invalid path - {}", path); - } - - Ok(()) + proxmox_apt::change_repository(&path, index, &options, digest) } const SUBDIRS: SubdirMap = &[ diff --git a/src/bin/pbs2to3.rs b/src/bin/pbs2to3.rs index 3b3714f6..1f895abd 100644 --- a/src/bin/pbs2to3.rs +++ b/src/bin/pbs2to3.rs @@ -5,7 +5,8 @@ use anyhow::{format_err, Error}; use regex::Regex; use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor}; -use proxmox_apt::repositories::{self, APTRepositoryFile, APTRepositoryPackageType}; +use proxmox_apt::repositories; +use proxmox_apt_api_types::{APTRepositoryFile, APTRepositoryPackageType}; use proxmox_backup::api2::node::apt; const OLD_SUITE: &str = "bullseye"; @@ -50,19 +51,18 @@ impl Checker { fn check_upgradable_packages(&mut self) -> Result<(), Error> { self.output.log_info("Checking for package updates..")?; - let result = Self::get_upgradable_packages(); + let result = apt::apt_update_available(); match result { Err(err) => { self.output.log_warn(format!("{err}"))?; self.output .log_fail("unable to retrieve list of package updates!")?; } - Ok(cache) => { - if cache.package_status.is_empty() { + Ok(package_status) => { + if package_status.is_empty() { self.output.log_pass("all packages up-to-date")?; } else { - let pkgs = cache - .package_status + let pkgs = package_status .iter() .map(|pkg| pkg.package.clone()) .collect::>() @@ -452,20 +452,6 @@ impl Checker { } Ok(()) } - - fn get_upgradable_packages() -> Result { - let cache = if let Ok(false) = proxmox_backup::tools::apt::pkg_cache_expired() { - if let Ok(Some(cache)) = proxmox_backup::tools::apt::read_pkg_state() { - cache - } else { - proxmox_backup::tools::apt::update_cache()? - } - } else { - proxmox_backup::tools::apt::update_cache()? - }; - - Ok(cache) - } } #[derive(PartialEq)] diff --git a/src/tools/apt.rs b/src/tools/apt.rs deleted file mode 100644 index 900843aa..00000000 --- a/src/tools/apt.rs +++ /dev/null @@ -1,293 +0,0 @@ -use std::collections::HashMap; -use std::collections::HashSet; - -use anyhow::{bail, format_err, Error}; -use apt_pkg_native::Cache; - -use proxmox_schema::const_regex; -use proxmox_sys::fs::{file_read_optional_string, replace_file, CreateOptions}; - -use pbs_api_types::APTUpdateInfo; -use pbs_buildcfg::PROXMOX_BACKUP_STATE_DIR_M; - -const APT_PKG_STATE_FN: &str = concat!(PROXMOX_BACKUP_STATE_DIR_M!(), "/pkg-state.json"); - -#[derive(Debug, serde::Serialize, serde::Deserialize)] -/// Some information we cache about the package (update) state, like what pending update version -/// we already notfied an user about -pub struct PkgState { - /// simple map from package name to most recently notified (emailed) version - pub notified: Option>, - /// A list of pending updates - pub package_status: Vec, -} - -pub fn write_pkg_cache(state: &PkgState) -> Result<(), Error> { - let serialized_state = serde_json::to_string(state)?; - - replace_file( - APT_PKG_STATE_FN, - serialized_state.as_bytes(), - CreateOptions::new(), - false, - ) - .map_err(|err| format_err!("Error writing package cache - {}", err))?; - Ok(()) -} - -pub fn read_pkg_state() -> Result, Error> { - let serialized_state = match file_read_optional_string(APT_PKG_STATE_FN) { - Ok(Some(raw)) => raw, - Ok(None) => return Ok(None), - Err(err) => bail!("could not read cached package state file - {}", err), - }; - - serde_json::from_str(&serialized_state) - .map(Some) - .map_err(|err| format_err!("could not parse cached package status - {}", err)) -} - -pub fn pkg_cache_expired() -> Result { - if let Ok(pbs_cache) = std::fs::metadata(APT_PKG_STATE_FN) { - let apt_pkgcache = std::fs::metadata("/var/cache/apt/pkgcache.bin")?; - let dpkg_status = std::fs::metadata("/var/lib/dpkg/status")?; - - let mtime = pbs_cache.modified()?; - - if apt_pkgcache.modified()? <= mtime && dpkg_status.modified()? <= mtime { - return Ok(false); - } - } - Ok(true) -} - -pub fn update_cache() -> Result { - // update our cache - let all_upgradeable = list_installed_apt_packages( - |data| { - data.candidate_version == data.active_version - && data.installed_version != Some(data.candidate_version) - }, - None, - ); - - let cache = match read_pkg_state() { - Ok(Some(mut cache)) => { - cache.package_status = all_upgradeable; - cache - } - _ => PkgState { - notified: None, - package_status: all_upgradeable, - }, - }; - write_pkg_cache(&cache)?; - Ok(cache) -} - -const_regex! { - VERSION_EPOCH_REGEX = r"^\d+:"; - FILENAME_EXTRACT_REGEX = r"^.*/.*?_(.*)_Packages$"; -} - -pub struct FilterData<'a> { - /// package name - pub package: &'a str, - /// this is version info returned by APT - pub installed_version: Option<&'a str>, - pub candidate_version: &'a str, - - /// this is the version info the filter is supposed to check - pub active_version: &'a str, -} - -enum PackagePreSelect { - OnlyInstalled, - OnlyNew, - All, -} - -pub fn list_installed_apt_packages bool>( - filter: F, - only_versions_for: Option<&str>, -) -> Vec { - let mut ret = Vec::new(); - let mut depends = HashSet::new(); - - // note: this is not an 'apt update', it just re-reads the cache from disk - let mut cache = Cache::get_singleton(); - cache.reload(); - - let mut cache_iter = match only_versions_for { - Some(name) => cache.find_by_name(name), - None => cache.iter(), - }; - - loop { - match cache_iter.next() { - Some(view) => { - let di = if only_versions_for.is_some() { - query_detailed_info(PackagePreSelect::All, &filter, view, None) - } else { - query_detailed_info( - PackagePreSelect::OnlyInstalled, - &filter, - view, - Some(&mut depends), - ) - }; - if let Some(info) = di { - ret.push(info); - } - - if only_versions_for.is_some() { - break; - } - } - None => { - drop(cache_iter); - // also loop through missing dependencies, as they would be installed - for pkg in depends.iter() { - let mut iter = cache.find_by_name(pkg); - let view = match iter.next() { - Some(view) => view, - None => continue, // package not found, ignore - }; - - let di = query_detailed_info(PackagePreSelect::OnlyNew, &filter, view, None); - if let Some(info) = di { - ret.push(info); - } - } - break; - } - } - } - - ret -} - -fn query_detailed_info<'a, F, V>( - pre_select: PackagePreSelect, - filter: F, - view: V, - depends: Option<&mut HashSet>, -) -> Option -where - F: Fn(FilterData) -> bool, - V: std::ops::Deref>, -{ - let current_version = view.current_version(); - let candidate_version = view.candidate_version(); - - let (current_version, candidate_version) = match pre_select { - PackagePreSelect::OnlyInstalled => match (current_version, candidate_version) { - (Some(cur), Some(can)) => (Some(cur), can), // package installed and there is an update - (Some(cur), None) => (Some(cur.clone()), cur), // package installed and up-to-date - (None, Some(_)) => return None, // package could be installed - (None, None) => return None, // broken - }, - PackagePreSelect::OnlyNew => match (current_version, candidate_version) { - (Some(_), Some(_)) => return None, - (Some(_), None) => return None, - (None, Some(can)) => (None, can), - (None, None) => return None, - }, - PackagePreSelect::All => match (current_version, candidate_version) { - (Some(cur), Some(can)) => (Some(cur), can), - (Some(cur), None) => (Some(cur.clone()), cur), - (None, Some(can)) => (None, can), - (None, None) => return None, - }, - }; - - // get additional information via nested APT 'iterators' - let mut view_iter = view.versions(); - while let Some(ver) = view_iter.next() { - let package = view.name(); - let version = ver.version(); - let mut origin_res = "unknown".to_owned(); - let mut section_res = "unknown".to_owned(); - let mut priority_res = "unknown".to_owned(); - let mut short_desc = package.clone(); - let mut long_desc = "".to_owned(); - - let fd = FilterData { - package: package.as_str(), - installed_version: current_version.as_deref(), - candidate_version: &candidate_version, - active_version: &version, - }; - - if filter(fd) { - if let Some(section) = ver.section() { - section_res = section; - } - - if let Some(prio) = ver.priority_type() { - priority_res = prio; - } - - // assume every package has only one origin file (not - // origin, but origin *file*, for some reason those seem to - // be different concepts in APT) - let mut origin_iter = ver.origin_iter(); - let origin = origin_iter.next(); - if let Some(origin) = origin { - if let Some(sd) = origin.short_desc() { - short_desc = sd; - } - - if let Some(ld) = origin.long_desc() { - long_desc = ld; - } - - // the package files appear in priority order, meaning - // the one for the candidate version is first - this is fine - // however, as the source package should be the same for all - // versions anyway - let mut pkg_iter = origin.file(); - let pkg_file = pkg_iter.next(); - if let Some(pkg_file) = pkg_file { - if let Some(origin_name) = pkg_file.origin() { - origin_res = origin_name; - } - } - } - - if let Some(depends) = depends { - let mut dep_iter = ver.dep_iter(); - loop { - let dep = match dep_iter.next() { - Some(dep) if dep.dep_type() != "Depends" => continue, - Some(dep) => dep, - None => break, - }; - - let dep_pkg = dep.target_pkg(); - let name = dep_pkg.name(); - - depends.insert(name); - } - } - - return Some(APTUpdateInfo { - package, - title: short_desc, - arch: view.arch(), - description: long_desc, - origin: origin_res, - version: candidate_version.clone(), - old_version: match current_version { - Some(vers) => vers, - None => "".to_owned(), - }, - priority: priority_res, - section: section_res, - extra_info: None, - }); - } - } - - None -} diff --git a/src/tools/mod.rs b/src/tools/mod.rs index f8c4f2d5..322894dd 100644 --- a/src/tools/mod.rs +++ b/src/tools/mod.rs @@ -6,7 +6,6 @@ use anyhow::{bail, Error}; use proxmox_http::{client::Client, HttpOptions, ProxyConfig}; -pub mod apt; pub mod config; pub mod disks; pub mod fs; -- 2.39.2 _______________________________________________ pbs-devel mailing list pbs-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pbs-devel