pacutils-sysroot - managing a mounted guest system

When a libalpm-based installation becomes broken to the point that the package manager itself can no longer be used, it must be fixed by loading a working environment and mounting the installation in need of repair as a guest. Typically this is done by setting the installation root to the guest with the --root option. Libalpm only uses the installation root to determine where to install and remove files during package transactions. It is completely independent of all other configuration options. This makes using --root unreliable for managing a mounted guest.

The first issue is that when changing the installation root, the host system's configuration is still used. Because the installation root is a configuration option ("RootDir") it cannot be used to load an alternate configuration file. Even if --config is added to load the guest configuration, any "Include"'d paths will still be resolved relative to the host's root. The only way to actually use the guest's configuration without using "chroot" is to use --config and manually add the mount path to all "Include" paths.

Now the guest configuration files are being used, but all configured paths will still refer to paths under to the host's root, not the guest's. If an installation root is explicitly provided, pacman and pacutils will set defaults for some, but not all, configuration paths to be underneath it. This typically works, because those paths are rarely explicitly set, so the defaults generally do the correct thing. If they have been set, however, the configured values will be used without modification. In order to reliably operate on the guest, all configuration paths must be set relative to the installation root (except for the database and log file paths which will default to paths inside the installation root if unset).

This is a significant amount of work just to run operate on a mounted system. In order to allow reliable operating on a mounted guest, pacman and pacutils have added the concept of a "sysroot". The sysroot is what is commonly intended when using the --root option; the program will operate as if the sysroot were actually the filesystem root. This is similar to using "chroot" to enter the mounted system before running the program, but still runs the host's copy. There are two ways to do this: chroot into the sysroot shortly after startup prior to reading any configuration or prepend the sysroot to all paths.

Using "chroot" directly can cause problems with libraries that use delayed loading of configuration files or shared objects. Notably, glibc delays loading certain objects until their functionality is actually used. This can cause a mismatch between components loaded prior to the "chroot" and those loaded after. To avoid this problem, libalpm should generally be configured to use paths under the sysroot without actually calling "chroot".

Pacutils provides sysroot-aware versions of its configuration parsing functions to simplify the process. Note that the sysroot is prepended to all paths during config resolution. Programs using the sysroot configuration parsing routines should NOT prepend the sysroot to configuration paths provided on the command line.

#include <alpm.h>
#include <getopt.h>
#include <pacutils.h>
enum {
       FLAG_CONFIG = 1,
int main(int argc, char *argv[]) {
 const char *config_file = "/etc/pacman.conf";
 const char *sysroot = NULL;
       alpm_handle_t *handle = NULL;
       pu_config_t *config = NULL;
       struct option long_opts[] = {
               { "config"        , required_argument , NULL    , FLAG_CONFIG        } ,
               { "sysroot"       , required_argument , NULL    , FLAG_SYSROOT       } ,
               { 0, 0, 0, 0 },
       while((c = getopt_long(argc, argv, short_opts, long_opts, NULL)) != -1) {
               switch(c) {
                       case FLAG_CONFIG: config_file = optarg; break;
                       case FLAG_SYSROOT: sysroot = optarg; break;
                       case '?': return 1; /* getopt_long already printed an error message */
       if((config = pu_ui_config_load_sysroot(NULL, config_file, sysroot)) == NULL)
               { return 1; } /* load_sysroot already printed an error message */
       if(!(handle = pu_initialize_handle_from_config(config))) {
               fprintf(stderr, "error: failed to initialize alpm.\n");
               return 1;
       return 0;
2024-04-16 pacutils