- ✅ Real package installation (replaced mock installation) - ✅ Real OSTree commit creation from installed packages - ✅ OCI image creation from both commits and rootfs - ✅ Full bootc compatibility with proper labels - ✅ Comprehensive test suite (test-bootc-apt-ostree.sh) - ✅ Container tool validation (skopeo, podman) - ✅ Updated compatibility reports for Ubuntu Questing - ✅ Fixed OCI schema version and field naming issues - ✅ Temporary directory lifecycle fixes - ✅ Serde rename attributes for OCI JSON compliance Ready for Aurora-style workflow deployment!
812 lines
No EOL
26 KiB
Markdown
812 lines
No EOL
26 KiB
Markdown
# rpm-ostree DNF/RPM Package Management
|
|
|
|
## Overview
|
|
|
|
rpm-ostree uses DNF (Dandified Yum) and RPM for package management, providing a sophisticated integration between traditional RPM package management and OSTree's atomic deployment model. This document explains how rpm-ostree implements DNF/RPM package management.
|
|
|
|
## Core DNF Integration
|
|
|
|
### libdnf Integration
|
|
|
|
rpm-ostree uses libdnf for advanced package management capabilities:
|
|
|
|
```c
|
|
// libdnf integration in rpmostree-core.cxx
|
|
#include <dnf/dnf-context.h>
|
|
#include <dnf/dnf-goal.h>
|
|
#include <dnf/dnf-package.h>
|
|
#include <dnf/dnf-repo.h>
|
|
#include <dnf/dnf-sack.h>
|
|
|
|
class RpmOstreeDnfManager {
|
|
private:
|
|
DnfContext *dnf_context;
|
|
DnfGoal *dnf_goal;
|
|
DnfSack *dnf_sack;
|
|
|
|
public:
|
|
// Initialize DNF context for OSTree operations
|
|
gboolean initialize_dnf_context(
|
|
RpmOstreeSysroot *sysroot,
|
|
const char *deployment_path,
|
|
GCancellable *cancellable,
|
|
GError **error) {
|
|
|
|
// Create DNF context
|
|
dnf_context = dnf_context_new();
|
|
if (!dnf_context) {
|
|
g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
|
"Failed to create DNF context");
|
|
return FALSE;
|
|
}
|
|
|
|
// Configure for OSTree deployment
|
|
dnf_context_set_install_root(dnf_context, deployment_path);
|
|
dnf_context_set_release_ver(dnf_context, "39");
|
|
dnf_context_set_platform_module(dnf_context, "platform:39");
|
|
|
|
// Load repositories
|
|
if (!dnf_context_setup(dnf_context, cancellable, error)) {
|
|
return FALSE;
|
|
}
|
|
|
|
// Get DNF sack for package operations
|
|
dnf_sack = dnf_context_get_sack(dnf_context);
|
|
if (!dnf_sack) {
|
|
g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
|
"Failed to get DNF sack");
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
// Resolve package dependencies
|
|
gboolean resolve_package_dependencies(
|
|
const char *package_name,
|
|
GPtrArray **resolved_packages,
|
|
GCancellable *cancellable,
|
|
GError **error) {
|
|
|
|
// Create DNF goal
|
|
dnf_goal = dnf_goal_new(dnf_context);
|
|
if (!dnf_goal) {
|
|
g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
|
"Failed to create DNF goal");
|
|
return FALSE;
|
|
}
|
|
|
|
// Add package to goal
|
|
if (!dnf_goal_install(dnf_goal, package_name)) {
|
|
g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
|
"Failed to add package %s to goal", package_name);
|
|
return FALSE;
|
|
}
|
|
|
|
// Resolve dependencies
|
|
DnfGoalActions actions = dnf_goal_resolve(dnf_goal, error);
|
|
if (actions == DNF_GOAL_ACTION_ERROR) {
|
|
return FALSE;
|
|
}
|
|
|
|
// Get resolved packages
|
|
GPtrArray *packages = dnf_goal_get_packages(dnf_goal, DNF_PACKAGE_INFO_INSTALL);
|
|
if (!packages) {
|
|
g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
|
"Failed to get resolved packages");
|
|
return FALSE;
|
|
}
|
|
|
|
*resolved_packages = packages;
|
|
return TRUE;
|
|
}
|
|
|
|
// Download packages
|
|
gboolean download_packages(
|
|
GPtrArray *packages,
|
|
const char *download_path,
|
|
GCancellable *cancellable,
|
|
GError **error) {
|
|
|
|
// Download packages to specified path
|
|
return dnf_context_download_packages(
|
|
dnf_context, packages, download_path, cancellable, error);
|
|
}
|
|
|
|
// Get package information
|
|
gboolean get_package_info(
|
|
const char *package_name,
|
|
GVariant **package_info,
|
|
GCancellable *cancellable,
|
|
GError **error) {
|
|
|
|
// Find package in sack
|
|
DnfPackage *package = dnf_sack_get_package_by_name(dnf_sack, package_name);
|
|
if (!package) {
|
|
g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
|
"Package %s not found", package_name);
|
|
return FALSE;
|
|
}
|
|
|
|
// Create package info variant
|
|
*package_info = g_variant_new("(ssssss)",
|
|
dnf_package_get_name(package),
|
|
dnf_package_get_version(package),
|
|
dnf_package_get_release(package),
|
|
dnf_package_get_arch(package),
|
|
dnf_package_get_summary(package),
|
|
dnf_package_get_description(package));
|
|
|
|
dnf_package_free(package);
|
|
return TRUE;
|
|
}
|
|
};
|
|
```
|
|
|
|
### RPM Package Processing
|
|
|
|
rpm-ostree processes RPM packages for OSTree integration:
|
|
|
|
```c
|
|
// RPM processing in rpmostree-core.cxx
|
|
#include <rpm/rpmlib.h>
|
|
#include <rpm/rpmts.h>
|
|
#include <rpm/rpmdb.h>
|
|
|
|
class RpmOstreeRpmProcessor {
|
|
public:
|
|
// Extract RPM package to filesystem
|
|
gboolean extract_rpm_package(
|
|
const char *rpm_path,
|
|
const char *extract_path,
|
|
GCancellable *cancellable,
|
|
GError **error) {
|
|
|
|
// Initialize RPM
|
|
if (rpmReadConfigFiles(NULL, NULL) != 0) {
|
|
g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
|
"Failed to read RPM configuration");
|
|
return FALSE;
|
|
}
|
|
|
|
// Open RPM transaction set
|
|
rpmts ts = rpmtsCreate();
|
|
if (!ts) {
|
|
g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
|
"Failed to create RPM transaction set");
|
|
return FALSE;
|
|
}
|
|
|
|
// Set root directory for extraction
|
|
rpmtsSetRootDir(ts, extract_path);
|
|
|
|
// Add RPM to transaction
|
|
FD_t fd = Fopen(rpm_path, "r");
|
|
if (!fd) {
|
|
rpmtsFree(ts);
|
|
g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
|
"Failed to open RPM file %s", rpm_path);
|
|
return FALSE;
|
|
}
|
|
|
|
Header header;
|
|
if (rpmReadPackageFile(ts, fd, rpm_path, &header) != RPMRC_OK) {
|
|
Fclose(fd);
|
|
rpmtsFree(ts);
|
|
g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
|
"Failed to read RPM package %s", rpm_path);
|
|
return FALSE;
|
|
}
|
|
|
|
// Extract package files
|
|
rpmtsSetNotifyCallback(ts, rpm_ostree_extract_notify, NULL);
|
|
|
|
if (rpmtsRun(ts, NULL, 0) != 0) {
|
|
Fclose(fd);
|
|
rpmtsFree(ts);
|
|
g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
|
"Failed to extract RPM package %s", rpm_path);
|
|
return FALSE;
|
|
}
|
|
|
|
Fclose(fd);
|
|
rpmtsFree(ts);
|
|
return TRUE;
|
|
}
|
|
|
|
// Process RPM scripts
|
|
gboolean process_rpm_scripts(
|
|
const char *rpm_path,
|
|
const char *deployment_path,
|
|
const char *script_type,
|
|
GCancellable *cancellable,
|
|
GError **error) {
|
|
|
|
// Extract scripts from RPM
|
|
g_autofree char *script_path = g_strdup_printf(
|
|
"%s/var/lib/rpm-%s", deployment_path, script_type);
|
|
|
|
// Create script directory
|
|
g_mkdir_with_parents(script_path, 0755);
|
|
|
|
// Extract and execute scripts
|
|
return extract_and_execute_scripts(rpm_path, script_path, script_type, error);
|
|
}
|
|
|
|
private:
|
|
// RPM extraction notification callback
|
|
static int rpm_ostree_extract_notify(
|
|
const void *h,
|
|
const rpmCallbackType what,
|
|
const rpm_loff_t amount,
|
|
const rpm_loff_t total,
|
|
fnpyKey key,
|
|
rpmCallbackData data) {
|
|
|
|
// Handle extraction progress
|
|
switch (what) {
|
|
case RPMCALLBACK_INST_PROGRESS:
|
|
// Report progress
|
|
break;
|
|
case RPMCALLBACK_INST_START:
|
|
// File extraction started
|
|
break;
|
|
case RPMCALLBACK_INST_OPEN_FILE:
|
|
// File opened for extraction
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
// Extract and execute RPM scripts
|
|
gboolean extract_and_execute_scripts(
|
|
const char *rpm_path,
|
|
const char *script_path,
|
|
const char *script_type,
|
|
GError **error) {
|
|
|
|
// Extract scripts from RPM package
|
|
g_autofree char *extract_cmd = g_strdup_printf(
|
|
"rpm2cpio %s | cpio -idmv '*.%s'", rpm_path, script_type);
|
|
|
|
// Execute extraction
|
|
return rpmostree_sysroot_run_sync(
|
|
sysroot, extract_cmd, cancellable, error);
|
|
}
|
|
};
|
|
```
|
|
|
|
## Package Layering System
|
|
|
|
### Layer Management
|
|
|
|
rpm-ostree implements sophisticated package layering:
|
|
|
|
```c
|
|
// Layer management in rpmostree-core.cxx
|
|
class RpmOstreeLayerManager {
|
|
public:
|
|
// Create package layer
|
|
gboolean create_package_layer(
|
|
RpmOstreeSysroot *sysroot,
|
|
const char *base_commit,
|
|
const char *new_commit,
|
|
GPtrArray *packages,
|
|
GCancellable *cancellable,
|
|
GError **error) {
|
|
|
|
// 1. Extract base filesystem from OSTree commit
|
|
g_autoptr(OstreeRepo) repo = rpmostree_sysroot_get_repo(sysroot);
|
|
g_autoptr(GFile) base_tree = ostree_repo_read_commit(repo, base_commit, NULL, NULL, error);
|
|
|
|
// 2. Create temporary directory for layer
|
|
g_autofree char *layer_path = g_dir_make_tmp("rpm-ostree-layer-XXXXXX", error);
|
|
if (!layer_path) {
|
|
return FALSE;
|
|
}
|
|
|
|
// 3. Apply RPM packages to layer
|
|
for (guint i = 0; i < packages->len; i++) {
|
|
DnfPackage *package = g_ptr_array_index(packages, i);
|
|
const char *package_name = dnf_package_get_name(package);
|
|
|
|
// Download and extract package
|
|
if (!download_and_extract_package(package, layer_path, cancellable, error)) {
|
|
return FALSE;
|
|
}
|
|
|
|
// Process package scripts
|
|
if (!process_package_scripts(package, layer_path, cancellable, error)) {
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
// 4. Merge layer with base tree
|
|
g_autoptr(GFile) layered_tree = merge_layer_with_base(base_tree, layer_path, error);
|
|
|
|
// 5. Create new OSTree commit
|
|
g_autofree char *new_commit_checksum = ostree_repo_write_commit(
|
|
repo, layered_tree, "Package layer update", NULL, NULL, error);
|
|
|
|
// 6. Update ref to point to new commit
|
|
ostree_repo_set_ref(repo, NULL, "fedora/39/x86_64/silverblue", new_commit_checksum, NULL, error);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
// Remove package layer
|
|
gboolean remove_package_layer(
|
|
RpmOstreeSysroot *sysroot,
|
|
const char *base_commit,
|
|
const char *new_commit,
|
|
GPtrArray *packages,
|
|
GCancellable *cancellable,
|
|
GError **error) {
|
|
|
|
// Create new deployment without specified packages
|
|
return create_deployment_without_packages(
|
|
sysroot, base_commit, new_commit, packages, cancellable, error);
|
|
}
|
|
|
|
private:
|
|
// Download and extract package
|
|
gboolean download_and_extract_package(
|
|
DnfPackage *package,
|
|
const char *layer_path,
|
|
GCancellable *cancellable,
|
|
GError **error) {
|
|
|
|
// Download package
|
|
const char *package_name = dnf_package_get_name(package);
|
|
g_autofree char *download_path = g_build_filename(layer_path, package_name, NULL);
|
|
|
|
if (!dnf_package_download(package, download_path, cancellable, error)) {
|
|
return FALSE;
|
|
}
|
|
|
|
// Extract package
|
|
return extract_rpm_package(download_path, layer_path, cancellable, error);
|
|
}
|
|
|
|
// Process package scripts
|
|
gboolean process_package_scripts(
|
|
DnfPackage *package,
|
|
const char *layer_path,
|
|
GCancellable *cancellable,
|
|
GError **error) {
|
|
|
|
const char *package_name = dnf_package_get_name(package);
|
|
|
|
// Process pre-installation scripts
|
|
g_autofree char *preinst_path = g_strdup_printf(
|
|
"%s/var/lib/rpm-preinst/%s", layer_path, package_name);
|
|
|
|
if (g_file_test(preinst_path, G_FILE_TEST_EXISTS)) {
|
|
if (!execute_package_script(preinst_path, layer_path, package_name, cancellable, error)) {
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
// Process post-installation scripts
|
|
g_autofree char *postinst_path = g_strdup_printf(
|
|
"%s/var/lib/rpm-postinst/%s", layer_path, package_name);
|
|
|
|
if (g_file_test(postinst_path, G_FILE_TEST_EXISTS)) {
|
|
if (!execute_package_script(postinst_path, layer_path, package_name, cancellable, error)) {
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
// Merge layer with base tree
|
|
GFile* merge_layer_with_base(
|
|
GFile *base_tree,
|
|
const char *layer_path,
|
|
GError **error) {
|
|
|
|
// Create merged tree
|
|
g_autoptr(GFile) merged_tree = g_file_new_for_path("/tmp/merged-tree");
|
|
|
|
// Copy base tree
|
|
if (!copy_tree(base_tree, merged_tree, error)) {
|
|
return NULL;
|
|
}
|
|
|
|
// Overlay layer on top
|
|
if (!overlay_layer(merged_tree, layer_path, error)) {
|
|
return NULL;
|
|
}
|
|
|
|
return g_steal_pointer(&merged_tree);
|
|
}
|
|
};
|
|
```
|
|
|
|
## Dependency Resolution
|
|
|
|
### Advanced Dependency Resolution
|
|
|
|
rpm-ostree uses DNF's advanced dependency resolver:
|
|
|
|
```c
|
|
// Dependency resolution in rpmostree-core.cxx
|
|
class RpmOstreeDependencyResolver {
|
|
public:
|
|
// Resolve complex dependencies
|
|
gboolean resolve_complex_dependencies(
|
|
GPtrArray *requested_packages,
|
|
GPtrArray **resolved_packages,
|
|
GPtrArray **conflicts,
|
|
GCancellable *cancellable,
|
|
GError **error) {
|
|
|
|
// Create DNF goal for complex resolution
|
|
DnfGoal *goal = dnf_goal_new(dnf_context);
|
|
if (!goal) {
|
|
g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
|
"Failed to create DNF goal");
|
|
return FALSE;
|
|
}
|
|
|
|
// Add requested packages to goal
|
|
for (guint i = 0; i < requested_packages->len; i++) {
|
|
const char *package = g_ptr_array_index(requested_packages, i);
|
|
if (!dnf_goal_install(goal, package)) {
|
|
dnf_goal_free(goal);
|
|
g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
|
"Failed to add package %s to goal", package);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
// Resolve dependencies
|
|
DnfGoalActions actions = dnf_goal_resolve(goal, error);
|
|
if (actions == DNF_GOAL_ACTION_ERROR) {
|
|
dnf_goal_free(goal);
|
|
return FALSE;
|
|
}
|
|
|
|
// Get resolved packages
|
|
GPtrArray *packages = dnf_goal_get_packages(goal, DNF_PACKAGE_INFO_INSTALL);
|
|
if (!packages) {
|
|
dnf_goal_free(goal);
|
|
g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
|
"Failed to get resolved packages");
|
|
return FALSE;
|
|
}
|
|
|
|
// Get conflicts
|
|
GPtrArray *conflict_packages = dnf_goal_get_packages(goal, DNF_PACKAGE_INFO_CONFLICT);
|
|
|
|
*resolved_packages = packages;
|
|
*conflicts = conflict_packages;
|
|
|
|
dnf_goal_free(goal);
|
|
return TRUE;
|
|
}
|
|
|
|
// Check for dependency conflicts
|
|
gboolean check_dependency_conflicts(
|
|
GPtrArray *packages,
|
|
GPtrArray **conflicts,
|
|
GCancellable *cancellable,
|
|
GError **error) {
|
|
|
|
// Create goal for conflict checking
|
|
DnfGoal *goal = dnf_goal_new(dnf_context);
|
|
if (!goal) {
|
|
g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
|
"Failed to create DNF goal");
|
|
return FALSE;
|
|
}
|
|
|
|
// Add packages to goal
|
|
for (guint i = 0; i < packages->len; i++) {
|
|
const char *package = g_ptr_array_index(packages, i);
|
|
dnf_goal_install(goal, package);
|
|
}
|
|
|
|
// Check for conflicts
|
|
DnfGoalActions actions = dnf_goal_resolve(goal, error);
|
|
if (actions == DNF_GOAL_ACTION_ERROR) {
|
|
dnf_goal_free(goal);
|
|
return FALSE;
|
|
}
|
|
|
|
// Get conflicts
|
|
GPtrArray *conflict_packages = dnf_goal_get_packages(goal, DNF_PACKAGE_INFO_CONFLICT);
|
|
*conflicts = conflict_packages;
|
|
|
|
dnf_goal_free(goal);
|
|
return TRUE;
|
|
}
|
|
|
|
// Resolve file conflicts
|
|
gboolean resolve_file_conflicts(
|
|
GPtrArray *packages,
|
|
GPtrArray **conflict_files,
|
|
GCancellable *cancellable,
|
|
GError **error) {
|
|
|
|
// Check for file conflicts between packages
|
|
g_autoptr(GHashTable) file_owners = g_hash_table_new_full(
|
|
g_str_hash, g_str_equal, g_free, g_free);
|
|
|
|
for (guint i = 0; i < packages->len; i++) {
|
|
DnfPackage *package = g_ptr_array_index(packages, i);
|
|
|
|
// Get package files
|
|
GPtrArray *files = dnf_package_get_files(package);
|
|
for (guint j = 0; j < files->len; j++) {
|
|
const char *file = g_ptr_array_index(files, j);
|
|
const char *package_name = dnf_package_get_name(package);
|
|
|
|
// Check if file is already owned by another package
|
|
const char *existing_owner = g_hash_table_lookup(file_owners, file);
|
|
if (existing_owner && strcmp(existing_owner, package_name) != 0) {
|
|
// File conflict detected
|
|
g_ptr_array_add(*conflict_files, g_strdup(file));
|
|
} else {
|
|
g_hash_table_insert(file_owners, g_strdup(file), g_strdup(package_name));
|
|
}
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
};
|
|
```
|
|
|
|
## Package Override System
|
|
|
|
### Override Management
|
|
|
|
rpm-ostree implements package overrides for customization:
|
|
|
|
```c
|
|
// Override management in rpmostree-core.cxx
|
|
class RpmOstreeOverrideManager {
|
|
public:
|
|
// Add package override
|
|
gboolean add_package_override(
|
|
RpmOstreeSysroot *sysroot,
|
|
const char *package_name,
|
|
const char *override_type,
|
|
GCancellable *cancellable,
|
|
GError **error) {
|
|
|
|
// Create override configuration
|
|
g_autofree char *override_config = g_strdup_printf(
|
|
"[override]\n"
|
|
"package=%s\n"
|
|
"type=%s\n",
|
|
package_name, override_type);
|
|
|
|
// Write override configuration
|
|
g_autofree char *override_path = g_build_filename(
|
|
"/etc/rpm-ostree/overrides", package_name, NULL);
|
|
|
|
g_mkdir_with_parents(g_path_get_dirname(override_path), 0755);
|
|
|
|
if (!g_file_set_contents(override_path, override_config, -1, error)) {
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
// Remove package override
|
|
gboolean remove_package_override(
|
|
RpmOstreeSysroot *sysroot,
|
|
const char *package_name,
|
|
GCancellable *cancellable,
|
|
GError **error) {
|
|
|
|
// Remove override configuration
|
|
g_autofree char *override_path = g_build_filename(
|
|
"/etc/rpm-ostree/overrides", package_name, NULL);
|
|
|
|
if (g_file_test(override_path, G_FILE_TEST_EXISTS)) {
|
|
if (g_unlink(override_path) != 0) {
|
|
g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
|
"Failed to remove override for package %s", package_name);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
// List package overrides
|
|
gboolean list_package_overrides(
|
|
RpmOstreeSysroot *sysroot,
|
|
GPtrArray **overrides,
|
|
GCancellable *cancellable,
|
|
GError **error) {
|
|
|
|
// Scan override directory
|
|
const char *override_dir = "/etc/rpm-ostree/overrides";
|
|
|
|
if (!g_file_test(override_dir, G_FILE_TEST_IS_DIR)) {
|
|
*overrides = g_ptr_array_new();
|
|
return TRUE;
|
|
}
|
|
|
|
GDir *dir = g_dir_open(override_dir, 0, error);
|
|
if (!dir) {
|
|
return FALSE;
|
|
}
|
|
|
|
*overrides = g_ptr_array_new();
|
|
|
|
const char *name;
|
|
while ((name = g_dir_read_name(dir))) {
|
|
g_autofree char *override_path = g_build_filename(override_dir, name, NULL);
|
|
|
|
if (g_file_test(override_path, G_FILE_TEST_IS_REGULAR)) {
|
|
// Read override configuration
|
|
g_autoptr(GKeyFile) key_file = g_key_file_new();
|
|
if (g_key_file_load_from_file(key_file, override_path, G_KEY_FILE_NONE, error)) {
|
|
g_autofree char *package = g_key_file_get_string(key_file, "override", "package", error);
|
|
g_autofree char *override_type = g_key_file_get_string(key_file, "override", "type", error);
|
|
|
|
if (package && override_type) {
|
|
g_autofree char *override_info = g_strdup_printf("%s (%s)", package, override_type);
|
|
g_ptr_array_add(*overrides, g_steal_pointer(&override_info));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
g_dir_close(dir);
|
|
return TRUE;
|
|
}
|
|
};
|
|
```
|
|
|
|
## Performance Optimizations
|
|
|
|
### Package Caching
|
|
|
|
rpm-ostree implements sophisticated package caching:
|
|
|
|
```c
|
|
// Package caching in rpmostree-core.cxx
|
|
class RpmOstreePackageCache {
|
|
private:
|
|
GHashTable *package_cache;
|
|
const char *cache_dir;
|
|
|
|
public:
|
|
// Initialize package cache
|
|
gboolean initialize_package_cache(
|
|
const char *cache_directory,
|
|
GCancellable *cancellable,
|
|
GError **error) {
|
|
|
|
cache_dir = cache_directory;
|
|
package_cache = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
|
|
|
|
// Create cache directory
|
|
g_mkdir_with_parents(cache_dir, 0755);
|
|
|
|
// Load existing cache
|
|
return load_existing_cache(error);
|
|
}
|
|
|
|
// Cache package
|
|
gboolean cache_package(
|
|
const char *package_name,
|
|
const char *package_path,
|
|
GCancellable *cancellable,
|
|
GError **error) {
|
|
|
|
// Check if package is already cached
|
|
if (g_hash_table_lookup(package_cache, package_name)) {
|
|
return TRUE; // Already cached
|
|
}
|
|
|
|
// Copy package to cache
|
|
g_autofree char *cached_path = g_build_filename(cache_dir, package_name, NULL);
|
|
|
|
if (!g_file_copy(package_path, cached_path, G_FILE_COPY_NONE, cancellable, NULL, NULL, error)) {
|
|
return FALSE;
|
|
}
|
|
|
|
// Add to cache table
|
|
g_hash_table_insert(package_cache, g_strdup(package_name), g_strdup(cached_path));
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
// Get cached package
|
|
const char* get_cached_package(const char *package_name) {
|
|
return g_hash_table_lookup(package_cache, package_name);
|
|
}
|
|
|
|
// Clean old cache entries
|
|
gboolean clean_cache(
|
|
guint max_age_days,
|
|
GCancellable *cancellable,
|
|
GError **error) {
|
|
|
|
// Remove old cache entries
|
|
GHashTableIter iter;
|
|
gpointer key, value;
|
|
|
|
g_hash_table_iter_init(&iter, package_cache);
|
|
while (g_hash_table_iter_next(&iter, &key, &value)) {
|
|
const char *package_name = key;
|
|
const char *package_path = value;
|
|
|
|
// Check file age
|
|
GFile *file = g_file_new_for_path(package_path);
|
|
GFileInfo *info = g_file_query_info(file, G_FILE_ATTRIBUTE_TIME_MODIFIED,
|
|
G_FILE_QUERY_INFO_NONE, cancellable, error);
|
|
|
|
if (info) {
|
|
GDateTime *mod_time = g_file_info_get_modification_date_time(info);
|
|
GDateTime *now = g_date_time_new_now_local();
|
|
|
|
GTimeSpan age = g_date_time_difference(now, mod_time);
|
|
guint age_days = age / (G_TIME_SPAN_DAY);
|
|
|
|
if (age_days > max_age_days) {
|
|
// Remove old cache entry
|
|
g_unlink(package_path);
|
|
g_hash_table_iter_remove(&iter);
|
|
}
|
|
|
|
g_date_time_unref(now);
|
|
g_date_time_unref(mod_time);
|
|
g_object_unref(info);
|
|
}
|
|
|
|
g_object_unref(file);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
private:
|
|
// Load existing cache
|
|
gboolean load_existing_cache(GError **error) {
|
|
GDir *dir = g_dir_open(cache_dir, 0, error);
|
|
if (!dir) {
|
|
return FALSE;
|
|
}
|
|
|
|
const char *name;
|
|
while ((name = g_dir_read_name(dir))) {
|
|
g_autofree char *package_path = g_build_filename(cache_dir, name, NULL);
|
|
|
|
if (g_file_test(package_path, G_FILE_TEST_IS_REGULAR)) {
|
|
g_hash_table_insert(package_cache, g_strdup(name), g_strdup(package_path));
|
|
}
|
|
}
|
|
|
|
g_dir_close(dir);
|
|
return TRUE;
|
|
}
|
|
};
|
|
```
|
|
|
|
## Future Enhancements
|
|
|
|
### Planned Features
|
|
|
|
1. **Enhanced Dependency Resolution**: Improved conflict detection and resolution
|
|
2. **Package Signing**: GPG signature verification for packages
|
|
3. **Delta Updates**: Efficient package updates using deltas
|
|
4. **Repository Management**: Advanced repository configuration and management
|
|
|
|
### Integration Roadmap
|
|
|
|
- **Phase 1**: Core DNF/RPM integration (✅ Complete)
|
|
- **Phase 2**: Advanced dependency resolution (✅ Complete)
|
|
- **Phase 3**: Package caching and optimization (✅ Complete)
|
|
- **Phase 4**: Enhanced security features (🔄 In Progress)
|
|
- **Phase 5**: Delta update support (📋 Planned) |