85b8b3bbb7
In the edge case when an updated package has different (or no) alternatives groups, make sure to prune those that are in pkgdb but not in the newly installed package. A potentially common case of this is when a package that formerly had alternatives gets removed and a transitional metapackage takes its place (which has no alternatives). When the new package has no dependencies, oldest next possible alternatives group will be used. This is because that indicates a removed package. When there are dependencies, the newest one will be used; as this indicates a transitional package.
610 lines
17 KiB
C
610 lines
17 KiB
C
/*-
|
|
* Copyright (c) 2015-2019 Juan Romero Pardines.
|
|
* Copyright (c) 2019 Duncan Overbruck.
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
|
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
|
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
|
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
|
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdbool.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <libgen.h>
|
|
|
|
#include "xbps_api_impl.h"
|
|
|
|
/**
|
|
* @file lib/package_alternatives.c
|
|
* @brief Alternatives generic routines
|
|
* @defgroup alternatives Alternatives generic functions
|
|
*
|
|
* These functions implement the alternatives framework.
|
|
*/
|
|
|
|
static char *
|
|
left(const char *str)
|
|
{
|
|
char *p;
|
|
size_t len;
|
|
|
|
p = strdup(str);
|
|
len = strlen(p) - strlen(strchr(p, ':'));
|
|
p[len] = '\0';
|
|
|
|
return p;
|
|
}
|
|
|
|
static const char *
|
|
right(const char *str)
|
|
{
|
|
return strchr(str, ':') + 1;
|
|
}
|
|
|
|
static const char *
|
|
normpath(char *path)
|
|
{
|
|
char *seg, *p;
|
|
|
|
for (p = path, seg = NULL; *p; p++) {
|
|
if (strncmp(p, "/../", 4) == 0 || strncmp(p, "/..", 4) == 0) {
|
|
memmove(seg ? seg : p, p+3, strlen(p+3) + 1);
|
|
return normpath(path);
|
|
} else if (strncmp(p, "/./", 3) == 0 || strncmp(p, "/.", 3) == 0) {
|
|
memmove(p, p+2, strlen(p+2) + 1);
|
|
} else if (strncmp(p, "//", 2) == 0 || strncmp(p, "/", 2) == 0) {
|
|
memmove(p, p+1, strlen(p+1) + 1);
|
|
}
|
|
if (*p == '/')
|
|
seg = p;
|
|
}
|
|
return path;
|
|
}
|
|
|
|
static char *
|
|
relpath(char *from, char *to)
|
|
{
|
|
int up;
|
|
char *p = to, *rel;
|
|
|
|
assert(from[0] == '/');
|
|
assert(to[0] == '/');
|
|
normpath(from);
|
|
normpath(to);
|
|
|
|
for (; *from == *to && *to; from++, to++) {
|
|
if (*to == '/')
|
|
p = to;
|
|
}
|
|
|
|
for (up = -1, from--; from && *from; from = strchr(from + 1, '/'), up++);
|
|
|
|
rel = calloc(3 * up + strlen(p), sizeof(char));
|
|
|
|
while (up--)
|
|
strcat(rel, "../");
|
|
if (*p)
|
|
strcat(rel, p+1);
|
|
return rel;
|
|
}
|
|
|
|
static int
|
|
remove_symlinks(struct xbps_handle *xhp, xbps_array_t a, const char *grname)
|
|
{
|
|
unsigned int i, cnt;
|
|
struct stat st;
|
|
|
|
cnt = xbps_array_count(a);
|
|
for (i = 0; i < cnt; i++) {
|
|
xbps_string_t str;
|
|
char *l, *lnk;
|
|
|
|
str = xbps_array_get(a, i);
|
|
l = left(xbps_string_cstring_nocopy(str));
|
|
assert(l);
|
|
if (l[0] != '/') {
|
|
const char *tgt;
|
|
char *tgt_dup, *tgt_dir;
|
|
tgt = right(xbps_string_cstring_nocopy(str));
|
|
tgt_dup = strdup(tgt);
|
|
assert(tgt_dup);
|
|
tgt_dir = dirname(tgt_dup);
|
|
lnk = xbps_xasprintf("%s%s/%s", xhp->rootdir, tgt_dir, l);
|
|
free(tgt_dup);
|
|
} else {
|
|
lnk = xbps_xasprintf("%s%s", xhp->rootdir, l);
|
|
}
|
|
if (lstat(lnk, &st) == -1 || !S_ISLNK(st.st_mode)) {
|
|
free(lnk);
|
|
free(l);
|
|
continue;
|
|
}
|
|
xbps_set_cb_state(xhp, XBPS_STATE_ALTGROUP_LINK_REMOVED, 0, NULL,
|
|
"Removing '%s' alternatives group symlink: %s", grname, l);
|
|
unlink(lnk);
|
|
free(lnk);
|
|
free(l);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
create_symlinks(struct xbps_handle *xhp, xbps_array_t a, const char *grname)
|
|
{
|
|
int rv;
|
|
unsigned int i, n;
|
|
char *alternative, *tok1, *tok2, *linkpath, *target, *dir, *p;
|
|
|
|
n = xbps_array_count(a);
|
|
|
|
for (i = 0; i < n; i++) {
|
|
alternative = xbps_string_cstring(xbps_array_get(a, i));
|
|
|
|
if (!(tok1 = strtok(alternative, ":")) ||
|
|
!(tok2 = strtok(NULL, ":"))) {
|
|
free(alternative);
|
|
return -1;
|
|
}
|
|
|
|
target = strdup(tok2);
|
|
dir = dirname(tok2);
|
|
|
|
/* add target dir to relative links */
|
|
if (tok1[0] != '/')
|
|
linkpath = xbps_xasprintf("%s/%s/%s", xhp->rootdir, dir, tok1);
|
|
else
|
|
linkpath = xbps_xasprintf("%s/%s", xhp->rootdir, tok1);
|
|
|
|
/* create target directory, necessary for dangling symlinks */
|
|
dir = xbps_xasprintf("%s/%s", xhp->rootdir, dir);
|
|
if (strcmp(dir, ".") && xbps_mkpath(dir, 0755) && errno != EEXIST) {
|
|
rv = errno;
|
|
xbps_dbg_printf(xhp,
|
|
"failed to create target dir '%s' for group '%s': %s\n",
|
|
dir, grname, strerror(errno));
|
|
free(dir);
|
|
goto err;
|
|
}
|
|
free(dir);
|
|
|
|
/* create link directory, necessary for dangling symlinks */
|
|
p = strdup(linkpath);
|
|
dir = dirname(p);
|
|
if (strcmp(dir, ".") && xbps_mkpath(dir, 0755) && errno != EEXIST) {
|
|
rv = errno;
|
|
xbps_dbg_printf(xhp,
|
|
"failed to create symlink dir '%s' for group '%s': %s\n",
|
|
dir, grname, strerror(errno));
|
|
free(p);
|
|
goto err;
|
|
}
|
|
free(p);
|
|
|
|
xbps_set_cb_state(xhp, XBPS_STATE_ALTGROUP_LINK_ADDED, 0, NULL,
|
|
"Creating '%s' alternatives group symlink: %s -> %s",
|
|
grname, tok1, target);
|
|
|
|
if (target[0] == '/') {
|
|
p = relpath(linkpath + strlen(xhp->rootdir), target);
|
|
free(target);
|
|
target = p;
|
|
}
|
|
|
|
unlink(linkpath);
|
|
if ((rv = symlink(target, linkpath)) != 0) {
|
|
xbps_dbg_printf(xhp,
|
|
"failed to create alt symlink '%s' for group '%s': %s\n",
|
|
linkpath, grname, strerror(errno));
|
|
goto err;
|
|
}
|
|
|
|
free(alternative);
|
|
free(target);
|
|
free(linkpath);
|
|
}
|
|
|
|
return 0;
|
|
|
|
err:
|
|
free(alternative);
|
|
free(target);
|
|
free(linkpath);
|
|
return rv;
|
|
}
|
|
|
|
int
|
|
xbps_alternatives_set(struct xbps_handle *xhp, const char *pkgname,
|
|
const char *group)
|
|
{
|
|
xbps_array_t allkeys;
|
|
xbps_dictionary_t alternatives, pkg_alternatives, pkgd, prevpkgd, prevpkg_alts;
|
|
const char *pkgver = NULL, *prevpkgname = NULL;
|
|
int rv = 0;
|
|
|
|
assert(xhp);
|
|
assert(pkgname);
|
|
|
|
alternatives = xbps_dictionary_get(xhp->pkgdb, "_XBPS_ALTERNATIVES_");
|
|
if (alternatives == NULL)
|
|
return ENOENT;
|
|
|
|
pkgd = xbps_pkgdb_get_pkg(xhp, pkgname);
|
|
if (pkgd == NULL)
|
|
return ENOENT;
|
|
|
|
pkg_alternatives = xbps_dictionary_get(pkgd, "alternatives");
|
|
if (!xbps_dictionary_count(pkg_alternatives))
|
|
return ENOENT;
|
|
|
|
if (group && !xbps_dictionary_get(pkg_alternatives, group))
|
|
return ENOENT;
|
|
|
|
xbps_dictionary_get_cstring_nocopy(pkgd, "pkgver", &pkgver);
|
|
|
|
allkeys = xbps_dictionary_all_keys(pkg_alternatives);
|
|
for (unsigned int i = 0; i < xbps_array_count(allkeys); i++) {
|
|
xbps_array_t array;
|
|
xbps_object_t keysym;
|
|
xbps_string_t kstr;
|
|
const char *keyname;
|
|
|
|
keysym = xbps_array_get(allkeys, i);
|
|
keyname = xbps_dictionary_keysym_cstring_nocopy(keysym);
|
|
|
|
if (group && strcmp(keyname, group))
|
|
continue;
|
|
|
|
array = xbps_dictionary_get(alternatives, keyname);
|
|
if (array == NULL)
|
|
continue;
|
|
|
|
/* remove symlinks from previous alternative */
|
|
xbps_array_get_cstring_nocopy(array, 0, &prevpkgname);
|
|
if (prevpkgname && strcmp(pkgname, prevpkgname) != 0) {
|
|
if ((prevpkgd = xbps_pkgdb_get_pkg(xhp, prevpkgname)) &&
|
|
(prevpkg_alts = xbps_dictionary_get(prevpkgd, "alternatives")) &&
|
|
xbps_dictionary_count(prevpkg_alts)) {
|
|
rv = remove_symlinks(xhp,
|
|
xbps_dictionary_get(prevpkg_alts, keyname),
|
|
keyname);
|
|
if (rv != 0)
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* put this alternative group at the head */
|
|
xbps_remove_string_from_array(array, pkgname);
|
|
kstr = xbps_string_create_cstring(pkgname);
|
|
xbps_array_add_first(array, kstr);
|
|
xbps_object_release(kstr);
|
|
|
|
/* apply the alternatives group */
|
|
xbps_set_cb_state(xhp, XBPS_STATE_ALTGROUP_ADDED, 0, NULL,
|
|
"%s: applying '%s' alternatives group", pkgver, keyname);
|
|
rv = create_symlinks(xhp, xbps_dictionary_get(pkg_alternatives, keyname), keyname);
|
|
if (rv != 0 || group)
|
|
break;
|
|
}
|
|
xbps_object_release(allkeys);
|
|
return rv;
|
|
}
|
|
|
|
static int
|
|
switch_alt_group(struct xbps_handle *xhp, const char *grpn, const char *pkgn,
|
|
xbps_dictionary_t *pkg_alternatives)
|
|
{
|
|
xbps_dictionary_t curpkgd, pkgalts;
|
|
|
|
curpkgd = xbps_pkgdb_get_pkg(xhp, pkgn);
|
|
assert(curpkgd);
|
|
|
|
xbps_set_cb_state(xhp, XBPS_STATE_ALTGROUP_SWITCHED, 0, NULL,
|
|
"Switched '%s' alternatives group to '%s'", grpn, pkgn);
|
|
pkgalts = xbps_dictionary_get(curpkgd, "alternatives");
|
|
if (pkg_alternatives) *pkg_alternatives = pkgalts;
|
|
return create_symlinks(xhp, xbps_dictionary_get(pkgalts, grpn), grpn);
|
|
}
|
|
|
|
int
|
|
xbps_alternatives_unregister(struct xbps_handle *xhp, xbps_dictionary_t pkgd)
|
|
{
|
|
xbps_array_t allkeys;
|
|
xbps_dictionary_t alternatives, pkg_alternatives;
|
|
const char *pkgver;
|
|
char *pkgname;
|
|
bool update = false;
|
|
int rv = 0;
|
|
|
|
assert(xhp);
|
|
|
|
alternatives = xbps_dictionary_get(xhp->pkgdb, "_XBPS_ALTERNATIVES_");
|
|
if (alternatives == NULL)
|
|
return 0;
|
|
|
|
pkg_alternatives = xbps_dictionary_get(pkgd, "alternatives");
|
|
if (!xbps_dictionary_count(pkg_alternatives))
|
|
return 0;
|
|
|
|
xbps_dictionary_get_cstring_nocopy(pkgd, "pkgver", &pkgver);
|
|
if ((pkgname = xbps_pkg_name(pkgver)) == NULL)
|
|
return EINVAL;
|
|
|
|
xbps_dictionary_get_bool(pkgd, "alternatives-update", &update);
|
|
|
|
allkeys = xbps_dictionary_all_keys(pkg_alternatives);
|
|
for (unsigned int i = 0; i < xbps_array_count(allkeys); i++) {
|
|
xbps_array_t array;
|
|
xbps_object_t keysym;
|
|
bool current = false;
|
|
const char *first = NULL, *keyname;
|
|
|
|
keysym = xbps_array_get(allkeys, i);
|
|
keyname = xbps_dictionary_keysym_cstring_nocopy(keysym);
|
|
|
|
array = xbps_dictionary_get(alternatives, keyname);
|
|
if (array == NULL)
|
|
continue;
|
|
|
|
xbps_array_get_cstring_nocopy(array, 0, &first);
|
|
if (strcmp(pkgname, first) == 0) {
|
|
/* this pkg is the current alternative for this group */
|
|
current = true;
|
|
rv = remove_symlinks(xhp,
|
|
xbps_dictionary_get(pkg_alternatives, keyname),
|
|
keyname);
|
|
if (rv != 0)
|
|
break;
|
|
}
|
|
|
|
if (!update) {
|
|
xbps_set_cb_state(xhp, XBPS_STATE_ALTGROUP_REMOVED, 0, NULL,
|
|
"%s: unregistered '%s' alternatives group", pkgver, keyname);
|
|
xbps_remove_string_from_array(array, pkgname);
|
|
xbps_array_get_cstring_nocopy(array, 0, &first);
|
|
}
|
|
|
|
if (xbps_array_count(array) == 0) {
|
|
xbps_dictionary_remove(alternatives, keyname);
|
|
continue;
|
|
}
|
|
|
|
if (update || !current)
|
|
continue;
|
|
|
|
/* get the new alternative group package */
|
|
if (switch_alt_group(xhp, keyname, first, &pkg_alternatives) != 0)
|
|
break;
|
|
}
|
|
xbps_object_release(allkeys);
|
|
free(pkgname);
|
|
|
|
return rv;
|
|
}
|
|
|
|
/*
|
|
* Prune the alternatives group from the db. This will first unregister
|
|
* it for the package and if there's no other package left providing the
|
|
* same, also ditch the whole group. When this is called, it is guranteed
|
|
* that what is happening is an upgrade, because it's only invoked when
|
|
* the repo and installed alternatives sets differ for a specific package.
|
|
*/
|
|
static void
|
|
prune_altgroup(struct xbps_handle *xhp, xbps_dictionary_t repod,
|
|
char *pkgname, const char *pkgver, const char *keyname) {
|
|
const char *newpkg, *curpkg;
|
|
xbps_array_t array;
|
|
xbps_dictionary_t alternatives;
|
|
xbps_string_t kstr;
|
|
unsigned int grp_count;
|
|
bool current = false;
|
|
|
|
xbps_set_cb_state(xhp, XBPS_STATE_ALTGROUP_REMOVED, 0, NULL,
|
|
"%s: unregistered '%s' alternatives group", pkgver, keyname);
|
|
|
|
alternatives = xbps_dictionary_get(xhp->pkgdb, "_XBPS_ALTERNATIVES_");
|
|
assert(alternatives);
|
|
array = xbps_dictionary_get(alternatives, keyname);
|
|
|
|
/* if using alt group from another package, we won't switch anything */
|
|
xbps_array_get_cstring_nocopy(array, 0, &curpkg);
|
|
current = (strcmp(pkgname, curpkg) == 0);
|
|
|
|
/* actually prune the alt group for the current package */
|
|
xbps_remove_string_from_array(array, pkgname);
|
|
grp_count = xbps_array_count(array);
|
|
if (grp_count == 0) {
|
|
/* it was the last one, ditch the whole thing */
|
|
xbps_dictionary_remove(alternatives, keyname);
|
|
return;
|
|
}
|
|
if (!current) {
|
|
/* not the last one, and ours wasn't the one being used */
|
|
return;
|
|
}
|
|
|
|
if (xbps_array_count(xbps_dictionary_get(repod, "run_depends")) == 0 &&
|
|
xbps_array_count(xbps_dictionary_get(repod, "shlib-requires")) == 0) {
|
|
/*
|
|
* Empty dependencies indicate a removed package (pure meta),
|
|
* use the first available group after ours has been pruned
|
|
*/
|
|
xbps_array_get_cstring_nocopy(array, 0, &newpkg);
|
|
switch_alt_group(xhp, keyname, newpkg, NULL);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Use the last group, as this indicates that a transitional metapackage
|
|
* is replacing the original and therefore a new package has registered
|
|
* a replacement group, which should be last in the array (most recent).
|
|
*/
|
|
xbps_array_get_cstring_nocopy(array, grp_count - 1, &newpkg);
|
|
|
|
/* put the new package as head */
|
|
kstr = xbps_string_create_cstring(newpkg);
|
|
xbps_remove_string_from_array(array, newpkg);
|
|
xbps_array_add_first(array, kstr);
|
|
xbps_array_get_cstring_nocopy(array, 0, &newpkg);
|
|
xbps_object_release(kstr);
|
|
|
|
switch_alt_group(xhp, keyname, newpkg, NULL);
|
|
}
|
|
|
|
|
|
static void
|
|
remove_obsoletes(struct xbps_handle *xhp, char *pkgname, const char *pkgver,
|
|
xbps_dictionary_t repod)
|
|
{
|
|
xbps_array_t allkeys;
|
|
xbps_dictionary_t pkgd, pkgd_alts, repod_alts;
|
|
|
|
pkgd = xbps_pkgdb_get_pkg(xhp, pkgname);
|
|
if (xbps_object_type(pkgd) != XBPS_TYPE_DICTIONARY) {
|
|
return;
|
|
}
|
|
|
|
pkgd_alts = xbps_dictionary_get(pkgd, "alternatives");
|
|
repod_alts = xbps_dictionary_get(repod, "alternatives");
|
|
|
|
if (xbps_object_type(pkgd_alts) != XBPS_TYPE_DICTIONARY) {
|
|
return;
|
|
}
|
|
|
|
allkeys = xbps_dictionary_all_keys(pkgd_alts);
|
|
for (unsigned int i = 0; i < xbps_array_count(allkeys); i++) {
|
|
xbps_array_t array, array_repo;
|
|
xbps_object_t keysym;
|
|
const char *keyname;
|
|
|
|
keysym = xbps_array_get(allkeys, i);
|
|
array = xbps_dictionary_get_keysym(pkgd_alts, keysym);
|
|
keyname = xbps_dictionary_keysym_cstring_nocopy(keysym);
|
|
|
|
array_repo = xbps_dictionary_get(repod_alts, keyname);
|
|
if (!xbps_array_equals(array, array_repo)) {
|
|
remove_symlinks(xhp, array, keyname);
|
|
}
|
|
|
|
/*
|
|
* There is nothing left in the alternatives group, which means
|
|
* the package is being upgraded and is removing it; if we don't
|
|
* prune it, the system will keep it set after removal of its
|
|
* parent package, but it will be empty and invalid...
|
|
*/
|
|
if (xbps_array_count(array_repo) == 0) {
|
|
prune_altgroup(xhp, repod, pkgname, pkgver, keyname);
|
|
}
|
|
}
|
|
xbps_object_release(allkeys);
|
|
}
|
|
|
|
int
|
|
xbps_alternatives_register(struct xbps_handle *xhp, xbps_dictionary_t pkg_repod)
|
|
{
|
|
xbps_array_t allkeys;
|
|
xbps_dictionary_t alternatives, pkg_alternatives;
|
|
const char *pkgver;
|
|
char *pkgname;
|
|
int rv = 0;
|
|
|
|
assert(xhp);
|
|
|
|
if (xhp->pkgdb == NULL)
|
|
return EINVAL;
|
|
|
|
alternatives = xbps_dictionary_get(xhp->pkgdb, "_XBPS_ALTERNATIVES_");
|
|
if (alternatives == NULL) {
|
|
alternatives = xbps_dictionary_create();
|
|
xbps_dictionary_set(xhp->pkgdb, "_XBPS_ALTERNATIVES_", alternatives);
|
|
xbps_object_release(alternatives);
|
|
}
|
|
alternatives = xbps_dictionary_get(xhp->pkgdb, "_XBPS_ALTERNATIVES_");
|
|
assert(alternatives);
|
|
|
|
xbps_dictionary_get_cstring_nocopy(pkg_repod, "pkgver", &pkgver);
|
|
pkgname = xbps_pkg_name(pkgver);
|
|
if (pkgname == NULL)
|
|
return EINVAL;
|
|
|
|
/*
|
|
* Compare alternatives from pkgdb and repo and then remove obsolete
|
|
* symlinks, also remove obsolete (empty) alternatives groups.
|
|
*/
|
|
remove_obsoletes(xhp, pkgname, pkgver, pkg_repod);
|
|
|
|
pkg_alternatives = xbps_dictionary_get(pkg_repod, "alternatives");
|
|
if (!xbps_dictionary_count(pkg_alternatives))
|
|
return 0;
|
|
|
|
allkeys = xbps_dictionary_all_keys(pkg_alternatives);
|
|
for (unsigned int i = 0; i < xbps_array_count(allkeys); i++) {
|
|
xbps_array_t array;
|
|
xbps_object_t keysym;
|
|
const char *keyname, *first;
|
|
|
|
keysym = xbps_array_get(allkeys, i);
|
|
keyname = xbps_dictionary_keysym_cstring_nocopy(keysym);
|
|
|
|
array = xbps_dictionary_get(alternatives, keyname);
|
|
if (array == NULL) {
|
|
array = xbps_array_create();
|
|
} else {
|
|
if (xbps_match_string_in_array(array, pkgname)) {
|
|
xbps_array_get_cstring_nocopy(array, 0, &first);
|
|
if (strcmp(pkgname, first)) {
|
|
/* current alternative does not match */
|
|
continue;
|
|
}
|
|
/* already registered, update symlinks */
|
|
rv = create_symlinks(xhp,
|
|
xbps_dictionary_get(pkg_alternatives, keyname),
|
|
keyname);
|
|
if (rv != 0)
|
|
break;
|
|
} else {
|
|
/* not registered, add provider */
|
|
xbps_array_add_cstring(array, pkgname);
|
|
xbps_set_cb_state(xhp, XBPS_STATE_ALTGROUP_ADDED, 0, NULL,
|
|
"%s: registered '%s' alternatives group", pkgver, keyname);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
xbps_array_add_cstring(array, pkgname);
|
|
xbps_dictionary_set(alternatives, keyname, array);
|
|
xbps_set_cb_state(xhp, XBPS_STATE_ALTGROUP_ADDED, 0, NULL,
|
|
"%s: registered '%s' alternatives group", pkgver, keyname);
|
|
/* apply alternatives for this group */
|
|
rv = create_symlinks(xhp,
|
|
xbps_dictionary_get(pkg_alternatives, keyname),
|
|
keyname);
|
|
xbps_object_release(array);
|
|
if (rv != 0)
|
|
break;
|
|
}
|
|
xbps_object_release(allkeys);
|
|
free(pkgname);
|
|
|
|
return rv;
|
|
}
|