[pbs-devel] applied: [PATCH backup] use new apt/apt-api-types crate
Wolfgang Bumiller
w.bumiller at proxmox.com
Tue Jul 9 08:19:27 CEST 2024
From: Dietmar Maurer <dietmar at proxmox.com>
---
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<String>,
-}
-
#[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<Value, Error> {
- 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<Vec<APTUpdateInfo>, 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<Option<ProxyConfig>, 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 at 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<String, Error> {
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<String>) -> Result<Value, Error> {
- 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<String, Error> {
+ proxmox_apt::get_changelog(&options)
}
#[api(
@@ -256,10 +163,8 @@ pub fn get_versions() -> Result<Vec<APTUpdateInfo>, 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<Vec<APTUpdateInfo>, Error> {
"zfsutils-linux",
];
- fn unknown_package(package: String, extra_info: Option<String>) -> 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<APTUpdateInfo> = 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<APTUpdateInfo> = 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<Vec<APTUpdateInfo>, 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<Value, Error> {
- 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<APTRepositoriesResult, Error> {
+ proxmox_apt::list_repositories("pbs")
}
#[api(
@@ -437,7 +216,7 @@ pub fn get_repositories() -> Result<Value, Error> {
type: APTRepositoryHandle,
},
digest: {
- schema: PROXMOX_CONFIG_DIGEST_SCHEMA,
+ type: ConfigDigest,
optional: true,
},
},
@@ -451,61 +230,11 @@ pub fn get_repositories() -> Result<Value, Error> {
/// 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<String>) -> 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<ConfigDigest>,
+) -> Result<(), Error> {
+ proxmox_apt::add_repository_handle("pbs", handle, digest)
}
#[api(
@@ -522,13 +251,12 @@ pub fn add_repository(handle: APTRepositoryHandle, digest: Option<String>) -> 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<String>) -> Re
pub fn change_repository(
path: String,
index: usize,
- enabled: Option<bool>,
- digest: Option<String>,
+ options: APTChangeRepositoryOptions,
+ digest: Option<ConfigDigest>,
) -> 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::<Vec<String>>()
@@ -452,20 +452,6 @@ impl Checker {
}
Ok(())
}
-
- fn get_upgradable_packages() -> Result<proxmox_backup::tools::apt::PkgState, Error> {
- 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<HashMap<String, String>>,
- /// A list of pending updates
- pub package_status: Vec<APTUpdateInfo>,
-}
-
-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<Option<PkgState>, 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<bool, Error> {
- 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<PkgState, Error> {
- // 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<F: Fn(FilterData) -> bool>(
- filter: F,
- only_versions_for: Option<&str>,
-) -> Vec<APTUpdateInfo> {
- 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<String>>,
-) -> Option<APTUpdateInfo>
-where
- F: Fn(FilterData) -> bool,
- V: std::ops::Deref<Target = apt_pkg_native::sane::PkgView<'a>>,
-{
- 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
More information about the pbs-devel
mailing list