apt-ostree/.notes/apt-dnf/rpm-ostree.md
robojerk d295f9bb4d Major milestone: Complete apt-ostree bootc compatibility and OCI integration
-  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!
2025-07-20 21:06:44 +00:00

26 KiB

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:

// 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:

// 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:

// 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:

// 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:

// 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:

// 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)