Lets update-alternatives manage symlinks inside a cross-arch root
filesystem in a directory specified by DPKG_ROOT.

Note: SYSCONFDIR has to be set to a value relative to DPKG_ROOT.

Signed-off-by: Andreas Oberritter <[email protected]>
---
 v4:
    Now using DPKG_ROOT variable (--force-script-chrootless option).
    Rebased onto master.
 v3 [https://lists.debian.org/debian-dpkg/2015/03/msg00004.html]:
    Rebased onto master.
 v2 [https://lists.debian.org/debian-dpkg/2014/09/msg00012.html]:
    altdb_get_namelist() didn't respect DPKG_INSTDIR.
 v1 [https://lists.debian.org/debian-dpkg/2014/08/msg00052.html]:

 utils/Makefile.am           |   1 +
 utils/update-alternatives.c | 175 ++++++++++++++++++++++++++++----------------
 2 files changed, 114 insertions(+), 62 deletions(-)

diff --git a/utils/Makefile.am b/utils/Makefile.am
index 83378ad..1f821c5 100644
--- a/utils/Makefile.am
+++ b/utils/Makefile.am
@@ -27,6 +27,7 @@ update_alternatives_SOURCES = \
 update_alternatives_CPPFLAGS = \
        -DALT_TMP_EXT=\".dpkg-tmp\" \
        -DADMINDIR_ENVVAR=\"DPKG_ADMINDIR\" \
+       -DINSTDIR_ENVVAR=\"DPKG_ROOT\" \
        $(AM_CPPFLAGS)
 
 update_alternatives_LDADD = \
diff --git a/utils/update-alternatives.c b/utils/update-alternatives.c
index 5dc3213..2b49819 100644
--- a/utils/update-alternatives.c
+++ b/utils/update-alternatives.c
@@ -51,6 +51,7 @@
 #define PROGNAME "update-alternatives"
 
 static const char *altdir = SYSCONFDIR "/alternatives";
+static const char *instdir;
 static const char *admdir;
 
 static const char *prog_path = "update-alternatives";
@@ -64,6 +65,27 @@ static int opt_verbose = 0;
 static int opt_force = 0;
 
 /*
+ * Types.
+ */
+
+enum alternative_path_status {
+       ALT_PATH_SYMLINK,
+       ALT_PATH_MISSING,
+       ALT_PATH_OTHER,
+};
+
+
+/*
+ * Predeclarations.
+ */
+
+static char * DPKG_ATTR_PRINTF(1)
+xasprintf(const char *fmt, ...);
+
+static enum alternative_path_status
+alternative_path_classify(const char *linkname);
+
+/*
  * Functions.
  */
 
@@ -270,7 +292,7 @@ xstrdup(const char *str)
 }
 
 static char *
-areadlink(const char *linkname)
+_areadlink(const char *linkname)
 {
        struct stat st;
        char *buf;
@@ -303,6 +325,19 @@ areadlink(const char *linkname)
 }
 
 static char *
+areadlink(const char *linkname)
+{
+       char *instdir_linkname;
+       char *ret;
+
+       instdir_linkname = xasprintf("%s%s", instdir, linkname);
+       ret = _areadlink(instdir_linkname);
+       free(instdir_linkname);
+
+       return ret;
+}
+
+static char *
 xreadlink(const char *linkname)
 {
        char *buf;
@@ -348,9 +383,22 @@ set_action(const char *new_action)
 }
 
 static const char *
+instdir_init(void)
+{
+       const char *dpkg_instdir;
+
+       dpkg_instdir = getenv(INSTDIR_ENVVAR);
+       if (dpkg_instdir)
+               return dpkg_instdir;
+
+       return "";
+}
+
+static const char *
 admindir_init(void)
 {
        const char *basedir, *basedir_env;
+       size_t length;
 
        /* Try to get the admindir from an environment variable, usually set
         * by the system package manager. */
@@ -360,6 +408,12 @@ admindir_init(void)
        else
                basedir = ADMINDIR;
 
+       /* If instdir is set and admindir is below instdir, treat admindir
+        * as relative. */
+       length = strlen(instdir);
+       if (strncmp(basedir, instdir, length) == 0)
+               basedir += length;
+
        return xasprintf("%s/%s", basedir, "alternatives");
 }
 
@@ -432,25 +486,43 @@ rename_mv(const char *src, const char *dst)
 static void
 checked_symlink(const char *filename, const char *linkname)
 {
-       if (symlink(filename, linkname))
+       char *instdir_linkname;
+
+       instdir_linkname = xasprintf("%s%s", instdir, linkname);
+
+       if (symlink(filename, instdir_linkname))
                syserr(_("error creating symbolic link '%.255s'"), linkname);
+
+       free(instdir_linkname);
 }
 
 static void
 checked_mv(const char *src, const char *dst)
 {
-       if (!rename_mv(src, dst))
+       char *instdir_src;
+       char *instdir_dst;
+
+       instdir_src = xasprintf("%s%s", instdir, src);
+       instdir_dst = xasprintf("%s%s", instdir, dst);
+
+       if (!rename_mv(instdir_src, instdir_dst))
                syserr(_("unable to install '%.250s' as '%.250s'"), src, dst);
+
+       free(instdir_src);
+       free(instdir_dst);
 }
 
 static void
 checked_rm(const char *f)
 {
-       if (!unlink(f))
-               return;
+       char *instdir_f;
 
-       if (errno != ENOENT)
+       instdir_f = xasprintf("%s%s", instdir, f);
+
+       if (unlink(instdir_f) && errno != ENOENT)
                syserr(_("unable to remove '%s'"), f);
+
+       free(instdir_f);
 }
 
 static void DPKG_ATTR_PRINTF(1)
@@ -567,16 +639,11 @@ fileset_has_slave(struct fileset *fs, const char *name)
 static bool
 fileset_can_install_slave(struct fileset *fs, const char *slave_name)
 {
-       struct stat st;
-
        /* Decide whether the slave alternative must be setup */
        if (fileset_has_slave(fs, slave_name)) {
                const char *slave = fileset_get_slave(fs, slave_name);
 
-               errno = 0;
-               if (stat(slave, &st) == -1 && errno != ENOENT)
-                       syserr(_("cannot stat file '%s'"), slave);
-               if (errno == 0)
+               if (alternative_path_classify(slave) != ALT_PATH_MISSING)
                        return true;
        }
 
@@ -1039,10 +1106,15 @@ static int
 altdb_get_namelist(struct dirent ***table)
 {
        int count;
+       char *instdir_admdir;
 
-       count = scandir(admdir, table, altdb_filter_namelist, alphasort);
+       instdir_admdir = xasprintf("%s%s", instdir, admdir);
+
+       count = scandir(instdir_admdir, table, altdb_filter_namelist, 
alphasort);
        if (count < 0)
-               syserr(_("cannot scan directory '%.255s'"), admdir);
+               syserr(_("cannot scan directory '%.255s'"), instdir_admdir);
+
+       free(instdir_admdir);
 
        return count;
 }
@@ -1167,7 +1239,6 @@ alternative_parse_fileset(struct alternative *a, struct 
altdb_context *ctx)
 {
        struct fileset *fs;
        struct slave_link *sl;
-       struct stat st;
        char *master_file;
 
        master_file = altdb_get_line(ctx, _("master file"));
@@ -1180,12 +1251,9 @@ alternative_parse_fileset(struct alternative *a, struct 
altdb_context *ctx)
        if (fs)
                ctx->bad_format(ctx, _("duplicate path %s"), master_file);
 
-       if (stat(master_file, &st)) {
+       if (alternative_path_classify(master_file) == ALT_PATH_MISSING) {
                char *junk;
 
-               if (errno != ENOENT)
-                       syserr(_("cannot stat file '%s'"), master_file);
-
                /* File not found - remove. */
                if (ctx->flags & ALTDB_WARN_PARSER)
                        warning(_("alternative %s (part of link group %s) "
@@ -1248,7 +1316,7 @@ alternative_load(struct alternative *a, enum altdb_flags 
flags)
                ctx.bad_format = altdb_parse_stop;
        else
                ctx.bad_format = altdb_parse_error;
-       ctx.filename = xasprintf("%s/%s", admdir, a->master_name);
+       ctx.filename = xasprintf("%s%s/%s", instdir, admdir, a->master_name);
 
        /* Open the alternative file. */
        ctx.fh = fopen(ctx.filename, "r");
@@ -1340,7 +1408,7 @@ alternative_save(struct alternative *a)
        file = xasprintf("%s/%s", admdir, a->master_name);
        filenew = xasprintf("%s" ALT_TMP_EXT, file);
 
-       ctx.filename = filenew;
+       ctx.filename = xasprintf("%s%s", instdir, filenew);
        ctx.fh = fopen(ctx.filename, "w");
        if (ctx.fh == NULL)
                syserr(_("unable to create file '%s'"), ctx.filename);
@@ -1379,6 +1447,7 @@ alternative_save(struct alternative *a)
                syserr(_("unable to sync file '%s'"), ctx.filename);
        if (fclose(ctx.fh))
                syserr(_("unable to close file '%s'"), ctx.filename);
+       free(ctx.filename);
 
        /* Put in place atomically. */
        checked_mv(filenew, file);
@@ -1399,7 +1468,6 @@ alternative_set_current(struct alternative *a, char 
*new_choice)
 static const char *
 alternative_get_current(struct alternative *a)
 {
-       struct stat st;
        char *curlink;
        char *file;
 
@@ -1407,12 +1475,9 @@ alternative_get_current(struct alternative *a)
                return a->current;
 
        curlink = xasprintf("%s/%s", altdir, a->master_name);
-       if (lstat(curlink, &st)) {
-               if (errno == ENOENT) {
-                       free(curlink);
-                       return alternative_set_current(a, NULL);
-               }
-               syserr(_("cannot stat file '%s'"), curlink);
+       if (alternative_path_classify(curlink) == ALT_PATH_MISSING) {
+               free(curlink);
+               return alternative_set_current(a, NULL);
        }
 
        file = xreadlink(curlink);
@@ -1674,14 +1739,8 @@ alternative_commit(struct alternative *a)
        alternative_commit_operations_free(a);
 }
 
-enum alternative_path_status {
-       ALT_PATH_SYMLINK,
-       ALT_PATH_MISSING,
-       ALT_PATH_OTHER,
-};
-
 static enum alternative_path_status
-alternative_path_classify(const char *linkname)
+_alternative_path_classify(const char *linkname)
 {
        struct stat st;
 
@@ -1697,6 +1756,19 @@ alternative_path_classify(const char *linkname)
        }
 }
 
+static enum alternative_path_status
+alternative_path_classify(const char *linkname)
+{
+       enum alternative_path_status ret;
+       char *instdir_linkname;
+
+       instdir_linkname = xasprintf("%s%s", instdir, linkname);
+       ret = _alternative_path_classify(instdir_linkname);
+       free(instdir_linkname);
+
+       return ret;
+}
+
 static bool
 alternative_path_can_remove(const char *linkname)
 {
@@ -2120,13 +2192,7 @@ alternative_select_mode(struct alternative *a, const 
char *current_choice)
        if (current_choice) {
                /* Detect manually modified alternative, switch to manual. */
                if (!alternative_has_choice(a, current_choice)) {
-                       struct stat st;
-
-                       errno = 0;
-                       if (stat(current_choice, &st) == -1 && errno != ENOENT)
-                               syserr(_("cannot stat file '%s'"), 
current_choice);
-
-                       if (errno == ENOENT) {
+                       if (alternative_path_classify(current_choice) == 
ALT_PATH_MISSING) {
                                warning(_("%s/%s is dangling; it will be 
updated "
                                          "with best choice"), altdir, 
a->master_name);
                                alternative_set_status(a, ALT_ST_AUTO);
@@ -2150,7 +2216,6 @@ alternative_evolve_slave(struct alternative *a, const 
char *cur_choice,
                          struct slave_link *sl, struct fileset *fs)
 {
        struct slave_link *sl_old;
-       struct stat st;
        char *new_file = NULL;
        const char *old, *new;
 
@@ -2174,17 +2239,7 @@ alternative_evolve_slave(struct alternative *a, const 
char *cur_choice,
        }
        if (strcmp(old, new) != 0 &&
            alternative_path_classify(old) == ALT_PATH_SYMLINK) {
-               bool rename_link = false;
-
-               if (new_file) {
-                       errno = 0;
-                       if (stat(new_file, &st) == -1 && errno != ENOENT)
-                               syserr(_("cannot stat file '%s'"),
-                                      new_file);
-                       rename_link = (errno == 0);
-               }
-
-               if (rename_link) {
+               if (new_file && alternative_path_classify(new_file) != 
ALT_PATH_MISSING) {
                        info(_("renaming %s slave link from %s to %s"),
                             sl->name, old, new);
                        checked_mv(old, new);
@@ -2474,7 +2529,6 @@ alternative_check_install_args(struct alternative 
*inst_alt,
        struct alternative_map *alt_map_links, *alt_map_parent;
        struct alternative *found;
        struct slave_link *sl;
-       struct stat st;
 
        alternative_check_name(inst_alt->master_name);
        alternative_check_link(inst_alt->master_link);
@@ -2499,13 +2553,9 @@ alternative_check_install_args(struct alternative 
*inst_alt,
                      inst_alt->master_link, found->master_name);
        }
 
-       if (stat(fileset->master_file, &st) == -1) {
-               if (errno == ENOENT)
-                       error(_("alternative path %s doesn't exist"),
-                             fileset->master_file);
-               else
-                       syserr(_("cannot stat file '%s'"), 
fileset->master_file);
-       }
+       if (alternative_path_classify(fileset->master_file) == ALT_PATH_MISSING)
+               error(_("alternative path %s doesn't exist"),
+                     fileset->master_file);
 
        for (sl = inst_alt->slaves; sl; sl = sl->next) {
                const char *file = fileset_get_slave(fileset, sl->name);
@@ -2580,6 +2630,7 @@ main(int argc, char **argv)
        bindtextdomain(PACKAGE, LOCALEDIR);
        textdomain(PACKAGE);
 
+       instdir = instdir_init();
        admdir = admindir_init();
 
        if (setvbuf(stdout, NULL, _IONBF, 0))

Reply via email to