diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 9fdd5356e7..3fefa2f5b2 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -389,6 +389,7 @@ dependencies = [ "agama-locale-data", "agama-transfer", "async-trait", + "camino", "cidr", "fluent-uri 0.4.1", "fs-err", diff --git a/rust/agama-software/src/model.rs b/rust/agama-software/src/model.rs index 82504b8334..726c0835dc 100644 --- a/rust/agama-software/src/model.rs +++ b/rust/agama-software/src/model.rs @@ -40,7 +40,7 @@ pub mod software_selection; pub mod state; pub use packages::{Resolvable, ResolvableType}; -pub use registration::{Registration, RegistrationBuilder}; +pub use registration::Registration; /// Abstract the software-related configuration from the underlying system. /// diff --git a/rust/agama-software/src/model/registration.rs b/rust/agama-software/src/model/registration.rs index 915111f7a7..941fd038c2 100644 --- a/rust/agama-software/src/model/registration.rs +++ b/rust/agama-software/src/model/registration.rs @@ -29,7 +29,6 @@ use agama_utils::{ actor::Handler, api::software::{AddonInfo, AddonRegistration, RegistrationInfo}, arch::Arch, - helpers::copy_dir_all, }; use camino::Utf8PathBuf; use openssl::x509::X509; @@ -210,14 +209,6 @@ impl Registration { .push(suseconnect_agama::DEFAULT_CONFIG_FILE.into()); self.copy_files(install_dir)?; - // FIXME: Copy services files. Temporarily solution because, most probably, - // it should be handled by libzypp itself. - if let Err(error) = copy_dir_all( - self.root_dir.join("etc/zypp/services.d"), - install_dir.join("etc/zypp/services.d"), - ) { - tracing::error!("Failed to copy the libzypp services files: {error}"); - }; Ok(()) } diff --git a/rust/agama-software/src/service.rs b/rust/agama-software/src/service.rs index 1c301e7b24..b757b02b25 100644 --- a/rust/agama-software/src/service.rs +++ b/rust/agama-software/src/service.rs @@ -29,7 +29,7 @@ use agama_utils::{ actor::{self, Actor, Handler, MessageHandler}, api::{ event::{self, Event}, - software::{Config, ProductConfig, Proposal, Repository, SystemInfo}, + software::{Config, Proposal, Repository, SystemInfo}, Issue, Scope, }, issue, diff --git a/rust/agama-software/src/zypp_server.rs b/rust/agama-software/src/zypp_server.rs index 771372c58d..32a9ed5b47 100644 --- a/rust/agama-software/src/zypp_server.rs +++ b/rust/agama-software/src/zypp_server.rs @@ -26,6 +26,7 @@ use agama_utils::{ software::{Pattern, SelectedBy, SoftwareProposal, SystemInfo}, Issue, Scope, }, + helpers::copy_dir_all, products::ProductSpec, progress, question, }; @@ -83,6 +84,9 @@ pub enum ZyppServerError { #[error("SSL error: {0}")] SSL(#[from] openssl::error::ErrorStack), + + #[error("Failed to copy to target system: {0}")] + IO(#[from] std::io::Error), } pub type ZyppServerResult = Result; @@ -260,6 +264,8 @@ impl ZyppServer { let repositories = zypp .list_repositories()? .into_iter() + // filter out service managed repositories + .filter(|repo| repo.service.is_none()) .map(|repo| state::Repository { name: repo.user_name, alias: repo.alias, @@ -504,11 +510,35 @@ impl ZyppServer { .map_err(|_| ZyppDispatchError::ResponseChannelClosed)?; return Ok(()); } + if let Err(error) = self.copy_files() { + tracing::warn!("Failed to copy zypp files: {error}"); + tx.send(Err(error.into())) + .map_err(|_| ZyppDispatchError::ResponseChannelClosed)?; + return Ok(()); + } + // if we fail to send ok, lets just ignore it let _ = tx.send(Ok(())); Ok(()) } + const ZYPP_DIRS: [&str; 4] = [ + "etc/zypp/services.d", + "etc/zypp/repos.d", + "etc/zypp/credentials.d", + "var/cache/zypp", + ]; + fn copy_files(&self) -> ZyppServerResult<()> { + for path in Self::ZYPP_DIRS { + let source_path = self.root_dir.join(path); + let target_path = self.install_dir.join(path); + if source_path.exists() { + copy_dir_all(&source_path, &target_path)?; + } + } + Ok(()) + } + fn modify_full_repo(&self, zypp: &zypp_agama::Zypp) -> ZyppServerResult<()> { let repos = zypp.list_repositories()?; // if url is invalid, then do not disable it and do not touch it diff --git a/rust/agama-utils/Cargo.toml b/rust/agama-utils/Cargo.toml index 01c1345fa1..c15ec38e34 100644 --- a/rust/agama-utils/Cargo.toml +++ b/rust/agama-utils/Cargo.toml @@ -8,6 +8,7 @@ edition.workspace = true agama-locale-data = { path = "../agama-locale-data" } agama-transfer = { path = "../agama-transfer" } async-trait = "0.1.89" +camino = "1.2.1" serde = { version = "1.0.228", features = ["derive"] } serde_json = "1.0.140" serde_with = "3.14.0" diff --git a/rust/agama-utils/src/lib.rs b/rust/agama-utils/src/lib.rs index ba88ef2f18..617bf4e0a5 100644 --- a/rust/agama-utils/src/lib.rs +++ b/rust/agama-utils/src/lib.rs @@ -44,20 +44,42 @@ pub fn gettext_noop(text: &str) -> &str { } pub mod helpers { - use std::{fs, io, path::Path}; + use camino::Utf8Path; + use fs_err as fs; - /// Copy the files in the `src` directory to `dst`. + /// Recursively copies a directory. /// - /// It does not perform a recursive copy. - pub fn copy_dir_all(src: impl AsRef, dst: impl AsRef) -> io::Result<()> { - fs::create_dir_all(&dst)?; - - for entry in fs::read_dir(src)? { + /// It copies the content of the `source` directory to the `target` directory. + /// It preserves the directory structure and the files content. + /// + /// Symlinks are recreated pointing to the same target as the original one. + pub fn copy_dir_all(source: &Utf8Path, target: &Utf8Path) -> Result<(), std::io::Error> { + fs::create_dir_all(&target)?; + for entry in source.read_dir_utf8()? { let entry = entry?; - let file_type = entry.file_type()?; - - if file_type.is_file() { - std::fs::copy(entry.path(), dst.as_ref().join(entry.file_name()))?; + let ty = fs::symlink_metadata(entry.path())?; + let dst = target.join(entry.file_name()); + if ty.is_dir() { + copy_dir_all(entry.path(), &dst)?; + } else if ty.is_symlink() { + // we need special handling of symlinks as libzypp do + // some tricks with danglinks symlinks and we should not + // break it + let link_dest = entry.path().read_link_utf8()?; + tracing::info!( + "Recreating symlink from {} to {} pointing to {}", + entry.path().to_string(), + dst.to_string(), + link_dest.to_string(), + ); + fs_err::os::unix::fs::symlink(link_dest, &dst)?; + } else { + tracing::info!( + "Copying from {} to {}", + entry.path().to_string(), + dst.to_string() + ); + fs::copy(entry.path(), &dst)?; } } diff --git a/rust/zypp-agama/src/lib.rs b/rust/zypp-agama/src/lib.rs index 2bcb1639c7..d8ee5fe9bc 100644 --- a/rust/zypp-agama/src/lib.rs +++ b/rust/zypp-agama/src/lib.rs @@ -23,6 +23,7 @@ pub struct Repository { pub url: String, pub alias: String, pub user_name: String, + pub service: Option, } impl Repository { @@ -242,11 +243,18 @@ impl Zypp { let size_usize: usize = repos.size.try_into().unwrap(); for i in 0..size_usize { let c_repo = *(repos.repos.add(i)); + let service = string_from_ptr(c_repo.serviceName); + let service_opt = if service.is_empty() { + None + } else { + Some(service) + }; let r_repo = Repository { enabled: c_repo.enabled, url: string_from_ptr(c_repo.url), alias: string_from_ptr(c_repo.alias), user_name: string_from_ptr(c_repo.userName), + service: service_opt, }; repos_v.push(r_repo); } diff --git a/rust/zypp-agama/zypp-agama-sys/c-layer/include/repository.h b/rust/zypp-agama/zypp-agama-sys/c-layer/include/repository.h index b2018d3565..d418659eb4 100644 --- a/rust/zypp-agama/zypp-agama-sys/c-layer/include/repository.h +++ b/rust/zypp-agama/zypp-agama-sys/c-layer/include/repository.h @@ -14,6 +14,7 @@ struct Repository { char *url; ///< owned char *alias; ///< owned char *userName; ///< owned + char *serviceName; ///< owned }; struct RepositoryList { diff --git a/rust/zypp-agama/zypp-agama-sys/c-layer/lib.cxx b/rust/zypp-agama/zypp-agama-sys/c-layer/lib.cxx index a29234bd16..4d32b6d022 100644 --- a/rust/zypp-agama/zypp-agama-sys/c-layer/lib.cxx +++ b/rust/zypp-agama/zypp-agama-sys/c-layer/lib.cxx @@ -255,6 +255,7 @@ void free_repository(struct Repository *repo) { free(repo->url); free(repo->alias); free(repo->userName); + free(repo->serviceName); } void free_repository_list(struct RepositoryList *list) noexcept { @@ -653,6 +654,7 @@ struct RepositoryList list_repositories(struct Zypp *zypp, new_repo->url = strdup(iter->url().asString().c_str()); new_repo->alias = strdup(iter->alias().c_str()); new_repo->userName = strdup(iter->asUserString().c_str()); + new_repo->serviceName = strdup(iter->service().c_str()); } struct RepositoryList result = {static_cast(size), repos}; diff --git a/rust/zypp-agama/zypp-agama-sys/src/bindings.rs b/rust/zypp-agama/zypp-agama-sys/src/bindings.rs index 7c3a76fb9c..43200b2246 100644 --- a/rust/zypp-agama/zypp-agama-sys/src/bindings.rs +++ b/rust/zypp-agama/zypp-agama-sys/src/bindings.rs @@ -547,16 +547,20 @@ pub struct Repository { pub alias: *mut ::std::os::raw::c_char, #[doc = "< owned"] pub userName: *mut ::std::os::raw::c_char, + #[doc = "< owned"] + pub serviceName: *mut ::std::os::raw::c_char, } #[allow(clippy::unnecessary_operation, clippy::identity_op)] const _: () = { - ["Size of Repository"][::std::mem::size_of::() - 32usize]; + ["Size of Repository"][::std::mem::size_of::() - 40usize]; ["Alignment of Repository"][::std::mem::align_of::() - 8usize]; ["Offset of field: Repository::enabled"][::std::mem::offset_of!(Repository, enabled) - 0usize]; ["Offset of field: Repository::url"][::std::mem::offset_of!(Repository, url) - 8usize]; ["Offset of field: Repository::alias"][::std::mem::offset_of!(Repository, alias) - 16usize]; ["Offset of field: Repository::userName"] [::std::mem::offset_of!(Repository, userName) - 24usize]; + ["Offset of field: Repository::serviceName"] + [::std::mem::offset_of!(Repository, serviceName) - 32usize]; }; #[repr(C)] #[derive(Debug, Copy, Clone)]