apt-ostree/.notes/rpm-ostree/how-commands-work/pkg-install.md

12 KiB

rpm-ostree install Command Flow Analysis

Overview

This document provides a detailed, step-by-step analysis of what happens when a user runs rpm-ostree install htop. The analysis is based on examination of the rpm-ostree source code and reveals the complex orchestration between client-side CLI parsing, D-Bus communication, daemon-side processing, and the underlying OSTree and DNF (libdnf) systems.

Command Entry Point

1. CLI Parsing and Validation

File: src/app/rpmostree-pkg-builtins.cxx Function: rpmostree_builtin_install()

gboolean
rpmostree_builtin_install (int argc, char **argv, RpmOstreeCommandInvocation *invocation,
                           GCancellable *cancellable, GError **error)

Steps:

  1. Option Parsing: Parse command-line options using GLib's GOptionContext

    • --reboot, --dry-run, --apply-live, --idempotent, etc.
    • Package names are extracted from positional arguments
  2. Argument Validation: Ensure at least one package is specified

    if (argc < 2) {
      rpmostree_usage_error (context, "At least one PACKAGE must be specified", error);
      return FALSE;
    }
    
  3. Interactive Confirmation: Handle --apply-live without --assumeyes

    • If running interactively, perform a dry-run first and prompt for confirmation
    • If non-interactive (script), auto-infer --assumeyes with a warning
  4. Container Detection: Check if running in an OSTree container

    • If in container, use different code path for container rebuilds
    • Otherwise, proceed with normal system installation

2. Core Package Change Logic

Function: pkg_change()

Steps:

  1. Package Classification: Determine package types

    gboolean met_local_pkg = FALSE;
    for (const char *const *it = packages_to_add; it && *it; it++)
      met_local_pkg = met_local_pkg || g_str_has_suffix (*it, ".rpm") || g_str_has_prefix (*it, "file://");
    
  2. Option Dictionary Creation: Build GVariant dictionary with all options

    GVariantDict dict;
    g_variant_dict_init (&dict, NULL);
    g_variant_dict_insert (&dict, "reboot", "b", opt_reboot);
    g_variant_dict_insert (&dict, "cache-only", "b", opt_cache_only);
    g_variant_dict_insert (&dict, "download-only", "b", opt_download_only);
    // ... more options
    
  3. API Selection: Choose between legacy and new D-Bus APIs

    • Legacy API: rpmostree_os_call_pkg_change_sync() for simple cases
    • New API: rpmostree_update_deployment() for complex cases (local packages, apply-live, etc.)

D-Bus Communication Layer

3. Client-Side D-Bus Setup

File: src/app/rpmostree-clientlib.cxx

Steps:

  1. Daemon Startup: Ensure rpm-ostreed daemon is running

    ROSCXX_TRY (client_start_daemon (), error);
    
  2. System Bus Connection: Connect to system D-Bus

    g_autoptr (GDBusConnection) connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, cancellable, error);
    
  3. Client Registration: Register as a client with the daemon

    g_dbus_connection_call_sync (connection, bus_name, sysroot_objpath, 
                                "org.projectatomic.rpmostree1.Sysroot", "RegisterClient", ...);
    
  4. OS Proxy Creation: Get proxy to the OS interface

    glnx_unref_object RPMOSTreeOS *os_proxy = NULL;
    if (!rpmostree_load_os_proxy (sysroot_proxy, opt_osname, cancellable, &os_proxy, error))
      return FALSE;
    

4. D-Bus Method Invocation

Legacy API Path (rpmostree_os_call_pkg_change_sync):

if (!rpmostree_os_call_pkg_change_sync (os_proxy, options, install_pkgs, uninstall_pkgs, NULL,
                                        &transaction_address, NULL, cancellable, error))
  return FALSE;

New API Path (rpmostree_update_deployment):

if (!rpmostree_update_deployment (os_proxy, NULL, /* refspec */
                                  NULL,           /* revision */
                                  install_pkgs, install_fileoverride_pkgs, uninstall_pkgs,
                                  NULL, /* override replace */
                                  NULL, /* override remove */
                                  NULL, /* override reset */
                                  NULL, /* local_repo_remote */
                                  NULL, /* treefile */
                                  options, &transaction_address, cancellable, error))
  return FALSE;

Daemon-Side Processing

5. D-Bus Method Handler

File: src/daemon/rpmostreed-os.cxx

Steps:

  1. Authorization Check: Verify user permissions via Polkit

    else if (g_strcmp0 (method_name, "PkgChange") == 0) {
      g_ptr_array_add (actions, (void *)"org.projectatomic.rpmostree1.install-uninstall-packages");
    }
    
  2. Transaction Management: Check for existing transactions or create new one

    if (!rpmostreed_sysroot_prep_for_txn (rsysroot, invocation, &transaction, &local_error))
      return os_throw_dbus_invocation_error (invocation, &local_error);
    
  3. Transaction Creation: Create appropriate transaction type

    • For package changes: rpmostreed_transaction_new_pkg_change()
    • For deployment updates: rpmostreed_transaction_new_update_deployment()
  4. Transaction Address Return: Return D-Bus object path for transaction monitoring

    const char *client_address = rpmostreed_transaction_get_client_address (transaction);
    rpmostree_os_complete_pkg_change (interface, invocation, client_address);
    

6. Transaction Processing

File: src/daemon/rpmostreed-transaction.cxx

Steps:

  1. Transaction State Management: Track transaction progress and state
  2. Dependency Resolution: Use libdnf to resolve package dependencies
  3. Package Download: Download required RPM packages from repositories
  4. OSTree Integration: Prepare for OSTree commit creation

Core Package Management

7. DNF Integration (libdnf)

Steps:

  1. Repository Setup: Initialize DNF context with configured repositories

    g_autoptr (DnfContext) dnfctx = dnf_context_new ();
    
  2. Package Resolution: Resolve package names to specific RPM packages

    hy_autoquery HyQuery query = hy_query_create (dnf_context_get_sack (dnfctx));
    hy_query_filter (query, HY_PKG_NAME, HY_EQ, package_name);
    
  3. Dependency Resolution: Calculate full dependency tree

    • Resolve all required dependencies
    • Handle conflicts and alternatives
    • Determine download order
  4. Package Download: Download RPM files to local cache

    • Use DNF's download infrastructure
    • Verify package integrity
    • Handle network failures and retries

8. OSTree Integration

Steps:

  1. Base Tree Preparation: Get current OSTree deployment as base

    OstreeDeployment *booted = ostree_sysroot_get_booted_deployment (sysroot);
    
  2. Package Import: Import downloaded RPMs into OSTree repository

    • Extract RPM contents
    • Apply to filesystem tree
    • Handle file conflicts and replacements
  3. Commit Creation: Create new OSTree commit with layered packages

    // Create new commit with package changes
    ostree_repo_prepare_transaction (repo, NULL, NULL, error);
    // ... package application logic ...
    ostree_repo_commit_transaction (repo, NULL, error);
    
  4. Metadata Generation: Generate commit metadata

    • Package lists and versions
    • Dependency information
    • Origin and timestamp data

Filesystem and Deployment

9. Filesystem Assembly

Steps:

  1. Base Tree Checkout: Checkout base OSTree commit to temporary location

  2. Package Application: Apply RPM contents to filesystem

    • Extract files from RPMs
    • Handle file permissions and ownership
    • Apply package scripts (preinst, postinst)
  3. Layer Management: Create layered filesystem structure

    • /usr - Base system files (immutable)
    • /var - Variable data (mutable)
    • /etc - Configuration (merged)
    • Package overlays in /usr/lib/rpm-ostree/
  4. Script Execution: Run package installation scripts

    • Pre-installation scripts
    • Post-installation scripts
    • Handle script failures and rollback

10. Deployment Management

Steps:

  1. Deployment Creation: Create new OSTree deployment

    ostree_sysroot_deploy_tree (sysroot, osname, new_commit, ...);
    
  2. Boot Configuration: Update bootloader configuration

    • Generate new initramfs if needed
    • Update kernel arguments
    • Configure boot entries
  3. State Persistence: Save deployment state

    • Update deployment index
    • Save package metadata
    • Update rollback information

Transaction Monitoring

11. Progress Reporting

Steps:

  1. Signal Emission: Emit D-Bus signals for progress

    // Progress signals
    rpmostree_transaction_emit_percent_progress (transaction, "Downloading packages", 50);
    rpmostree_transaction_emit_message (transaction, "Installing htop-2.2.0-1.fc33.x86_64");
    
  2. Client Monitoring: Client receives and displays progress

    // Client connects to transaction and monitors signals
    rpmostree_transaction_client_run (invocation, sysroot_proxy, os_proxy, options, ...);
    
  3. Error Handling: Handle failures and provide rollback

    • Network failures during download
    • Package conflicts
    • Script execution failures
    • OSTree commit failures

12. Transaction Completion

Steps:

  1. Success Path:

    • Mark transaction as successful
    • Emit completion signals
    • Clean up temporary files
    • Update system state
  2. Failure Path:

    • Rollback to previous state
    • Clean up partial changes
    • Emit error signals
    • Provide error details
  3. Reboot Handling: If --reboot specified

    if (opt_reboot) {
      // Schedule reboot after transaction completion
      rpmostree_sysroot_deploy_tree (sysroot, osname, new_commit, 
                                    OSTREE_SYSROOT_DEPLOY_FLAGS_REBOOT, ...);
    }
    

Final Steps

13. System State Update

Steps:

  1. Deployment Activation: Activate new deployment

    • Update bootloader entries
    • Set new deployment as default
    • Preserve rollback deployment
  2. Cache Updates: Update package metadata cache

    • Refresh repository metadata
    • Update package lists
    • Cache dependency information
  3. Cleanup: Remove temporary files and caches

    • Clean downloaded RPMs
    • Remove temporary directories
    • Update system journal

14. User Feedback

Steps:

  1. Status Display: Show final status

    # Example output
    Checking out packages...done
    Running pre scripts...done
    Running post scripts...done
    Writing rpmdb...done
    Writing OSTree commit...done
    
  2. Deployment Information: Display deployment details

    • New deployment checksum
    • Package changes summary
    • Reboot requirement (if applicable)
  3. Next Steps: Provide guidance for user

    • Reboot instructions if needed
    • Package verification commands
    • Rollback instructions

Key Architectural Insights

1. Client-Daemon Architecture

  • CLI client handles user interaction and D-Bus communication
  • Daemon (rpm-ostreed) handles all privileged operations
  • Clear separation of concerns and security boundaries

2. Transaction-Based Design

  • All operations wrapped in transactions
  • Atomic operations with rollback capability
  • Progress monitoring and cancellation support

3. Layered Filesystem Model

  • Base OSTree commit remains immutable
  • Package changes applied as overlays
  • Clear separation between system and user packages

4. Dependency Management

  • Full dependency resolution via libdnf
  • Handles complex dependency graphs
  • Conflict resolution and alternatives

5. Error Handling and Recovery

  • Comprehensive error handling at each stage
  • Automatic rollback on failures
  • Detailed error reporting and logging

This analysis reveals the sophisticated architecture of rpm-ostree, which combines the atomic deployment model of OSTree with the powerful package management capabilities of DNF, all orchestrated through a robust client-daemon architecture with comprehensive transaction management.