XBPS Library API 20240111
The X Binary Package System
package_alternatives.c
1/*-
2 * Copyright (c) 2015-2019 Juan Romero Pardines.
3 * Copyright (c) 2019 Duncan Overbruck.
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
16 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
17 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
18 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
19 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
20 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
22 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
24 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include <sys/stat.h>
28
29#include <errno.h>
30#include <libgen.h>
31#include <stdbool.h>
32#include <stdio.h>
33#include <stdlib.h>
34#include <string.h>
35#include <unistd.h>
36
37#include "xbps_api_impl.h"
38
39/**
40 * @file lib/package_alternatives.c
41 * @brief Alternatives generic routines
42 * @defgroup alternatives Alternatives generic functions
43 *
44 * These functions implement the alternatives framework.
45 */
46
47static char *
48left(const char *str)
49{
50 char *p;
51 size_t len;
52
53 p = strdup(str);
54 len = strlen(p) - strlen(strchr(p, ':'));
55 p[len] = '\0';
56
57 return p;
58}
59
60static const char *
61right(const char *str)
62{
63 return strchr(str, ':') + 1;
64}
65
66static const char *
67normpath(char *path)
68{
69 char *seg, *p;
70
71 for (p = path, seg = NULL; *p; p++) {
72 if (strncmp(p, "/../", 4) == 0 || strncmp(p, "/..", 4) == 0) {
73 memmove(seg ? seg : p, p+3, strlen(p+3) + 1);
74 return normpath(path);
75 } else if (strncmp(p, "/./", 3) == 0 || strncmp(p, "/.", 3) == 0) {
76 memmove(p, p+2, strlen(p+2) + 1);
77 } else if (strncmp(p, "//", 2) == 0 || strncmp(p, "/", 2) == 0) {
78 memmove(p, p+1, strlen(p+1) + 1);
79 }
80 if (*p == '/')
81 seg = p;
82 }
83 return path;
84}
85
86static char *
87relpath(char *from, char *to)
88{
89 int up;
90 char *p = to, *rel;
91
92 assert(from[0] == '/');
93 assert(to[0] == '/');
94 normpath(from);
95 normpath(to);
96
97 for (; *from == *to && *to; from++, to++) {
98 if (*to == '/')
99 p = to;
100 }
101
102 for (up = -1, from--; from && *from; from = strchr(from + 1, '/'), up++);
103
104 rel = calloc(3 * up + strlen(p), 1);
105 if (!rel)
106 return NULL;
107
108 while (up--)
109 strcat(rel, "../");
110 if (*p)
111 strcat(rel, p+1);
112 return rel;
113}
114
115static int
116remove_symlinks(struct xbps_handle *xhp, xbps_array_t a, const char *grname)
117{
118 unsigned int i, cnt;
119 struct stat st;
120
121 cnt = xbps_array_count(a);
122 for (i = 0; i < cnt; i++) {
123 xbps_string_t str;
124 char *l, *lnk;
125
126 str = xbps_array_get(a, i);
127 l = left(xbps_string_cstring_nocopy(str));
128 assert(l);
129 if (l[0] != '/') {
130 const char *tgt;
131 char *tgt_dup, *tgt_dir;
132 tgt = right(xbps_string_cstring_nocopy(str));
133 tgt_dup = strdup(tgt);
134 assert(tgt_dup);
135 tgt_dir = dirname(tgt_dup);
136 lnk = xbps_xasprintf("%s%s/%s", xhp->rootdir, tgt_dir, l);
137 free(tgt_dup);
138 } else {
139 lnk = xbps_xasprintf("%s%s", xhp->rootdir, l);
140 }
141 if (lstat(lnk, &st) == -1 || !S_ISLNK(st.st_mode)) {
142 free(lnk);
143 free(l);
144 continue;
145 }
146 xbps_set_cb_state(xhp, XBPS_STATE_ALTGROUP_LINK_REMOVED, 0, NULL,
147 "Removing '%s' alternatives group symlink: %s", grname, l);
148 unlink(lnk);
149 free(lnk);
150 free(l);
151 }
152
153 return 0;
154}
155
156static int
157create_symlinks(struct xbps_handle *xhp, xbps_array_t a, const char *grname)
158{
159 int rv;
160 unsigned int i, n;
161 char *alternative, *tok1, *tok2, *linkpath, *target, *dir, *p;
162
163 n = xbps_array_count(a);
164
165 for (i = 0; i < n; i++) {
166 alternative = xbps_string_cstring(xbps_array_get(a, i));
167
168 if (!(tok1 = strtok(alternative, ":")) ||
169 !(tok2 = strtok(NULL, ":"))) {
170 free(alternative);
171 return -1;
172 }
173
174 target = strdup(tok2);
175 dir = dirname(tok2);
176
177 /* add target dir to relative links */
178 if (tok1[0] != '/')
179 linkpath = xbps_xasprintf("%s/%s/%s", xhp->rootdir, dir, tok1);
180 else
181 linkpath = xbps_xasprintf("%s/%s", xhp->rootdir, tok1);
182
183 /* create target directory, necessary for dangling symlinks */
184 dir = xbps_xasprintf("%s/%s", xhp->rootdir, dir);
185 if (strcmp(dir, ".") && xbps_mkpath(dir, 0755) && errno != EEXIST) {
186 rv = errno;
187 xbps_dbg_printf(
188 "failed to create target dir '%s' for group '%s': %s\n",
189 dir, grname, strerror(errno));
190 free(dir);
191 goto err;
192 }
193 free(dir);
194
195 /* create link directory, necessary for dangling symlinks */
196 p = strdup(linkpath);
197 dir = dirname(p);
198 if (strcmp(dir, ".") && xbps_mkpath(dir, 0755) && errno != EEXIST) {
199 rv = errno;
200 xbps_dbg_printf(
201 "failed to create symlink dir '%s' for group '%s': %s\n",
202 dir, grname, strerror(errno));
203 free(p);
204 goto err;
205 }
206 free(p);
207
208 xbps_set_cb_state(xhp, XBPS_STATE_ALTGROUP_LINK_ADDED, 0, NULL,
209 "Creating '%s' alternatives group symlink: %s -> %s",
210 grname, tok1, target);
211
212 if (target[0] == '/') {
213 p = relpath(linkpath + strlen(xhp->rootdir), target);
214 free(target);
215 target = p;
216 }
217
218 unlink(linkpath);
219 if ((rv = symlink(target, linkpath)) != 0) {
220 xbps_dbg_printf(
221 "failed to create alt symlink '%s' for group '%s': %s\n",
222 linkpath, grname, strerror(errno));
223 goto err;
224 }
225
226 free(alternative);
227 free(target);
228 free(linkpath);
229 }
230
231 return 0;
232
233err:
234 free(alternative);
235 free(target);
236 free(linkpath);
237 return rv;
238}
239
240int
241xbps_alternatives_set(struct xbps_handle *xhp, const char *pkgname,
242 const char *group)
243{
244 xbps_array_t allkeys;
245 xbps_dictionary_t alternatives, pkg_alternatives, pkgd, prevpkgd, prevpkg_alts;
246 const char *pkgver = NULL, *prevpkgname = NULL;
247 int rv = 0;
248
249 assert(xhp);
250 assert(pkgname);
251
252 alternatives = xbps_dictionary_get(xhp->pkgdb, "_XBPS_ALTERNATIVES_");
253 if (alternatives == NULL)
254 return ENOENT;
255
256 pkgd = xbps_pkgdb_get_pkg(xhp, pkgname);
257 if (pkgd == NULL)
258 return ENOENT;
259
260 pkg_alternatives = xbps_dictionary_get(pkgd, "alternatives");
261 if (!xbps_dictionary_count(pkg_alternatives))
262 return ENOENT;
263
264 if (group && !xbps_dictionary_get(pkg_alternatives, group))
265 return ENOENT;
266
267 xbps_dictionary_get_cstring_nocopy(pkgd, "pkgver", &pkgver);
268
269 allkeys = xbps_dictionary_all_keys(pkg_alternatives);
270 for (unsigned int i = 0; i < xbps_array_count(allkeys); i++) {
271 xbps_array_t array;
272 xbps_object_t keysym;
273 xbps_string_t kstr;
274 const char *keyname;
275
276 keysym = xbps_array_get(allkeys, i);
277 keyname = xbps_dictionary_keysym_cstring_nocopy(keysym);
278
279 if (group && strcmp(keyname, group))
280 continue;
281
282 array = xbps_dictionary_get(alternatives, keyname);
283 if (array == NULL)
284 continue;
285
286 /* remove symlinks from previous alternative */
287 xbps_array_get_cstring_nocopy(array, 0, &prevpkgname);
288 if (prevpkgname && strcmp(pkgname, prevpkgname) != 0) {
289 if ((prevpkgd = xbps_pkgdb_get_pkg(xhp, prevpkgname)) &&
290 (prevpkg_alts = xbps_dictionary_get(prevpkgd, "alternatives")) &&
291 xbps_dictionary_count(prevpkg_alts)) {
292 rv = remove_symlinks(xhp,
293 xbps_dictionary_get(prevpkg_alts, keyname),
294 keyname);
295 if (rv != 0)
296 break;
297 }
298 }
299
300 /* put this alternative group at the head */
301 xbps_remove_string_from_array(array, pkgname);
302 kstr = xbps_string_create_cstring(pkgname);
303 xbps_array_add_first(array, kstr);
304 xbps_object_release(kstr);
305
306 /* apply the alternatives group */
307 xbps_set_cb_state(xhp, XBPS_STATE_ALTGROUP_ADDED, 0, NULL,
308 "%s: applying '%s' alternatives group", pkgver, keyname);
309 rv = create_symlinks(xhp, xbps_dictionary_get(pkg_alternatives, keyname), keyname);
310 if (rv != 0 || group)
311 break;
312 }
313 xbps_object_release(allkeys);
314 return rv;
315}
316
317static int
318switch_alt_group(struct xbps_handle *xhp, const char *grpn, const char *pkgn,
319 xbps_dictionary_t *pkg_alternatives)
320{
321 xbps_dictionary_t curpkgd, pkgalts;
322
323 curpkgd = xbps_pkgdb_get_pkg(xhp, pkgn);
324 assert(curpkgd);
325
326 xbps_set_cb_state(xhp, XBPS_STATE_ALTGROUP_SWITCHED, 0, NULL,
327 "Switched '%s' alternatives group to '%s'", grpn, pkgn);
328 pkgalts = xbps_dictionary_get(curpkgd, "alternatives");
329 if (pkg_alternatives) *pkg_alternatives = pkgalts;
330 return create_symlinks(xhp, xbps_dictionary_get(pkgalts, grpn), grpn);
331}
332
333int
334xbps_alternatives_unregister(struct xbps_handle *xhp, xbps_dictionary_t pkgd)
335{
336 xbps_array_t allkeys;
337 xbps_dictionary_t alternatives, pkg_alternatives;
338 const char *pkgver, *pkgname;
339 bool update = false;
340 int rv = 0;
341
342 assert(xhp);
343
344 alternatives = xbps_dictionary_get(xhp->pkgdb, "_XBPS_ALTERNATIVES_");
345 if (alternatives == NULL)
346 return 0;
347
348 pkg_alternatives = xbps_dictionary_get(pkgd, "alternatives");
349 if (!xbps_dictionary_count(pkg_alternatives))
350 return 0;
351
352 xbps_dictionary_get_cstring_nocopy(pkgd, "pkgver", &pkgver);
353 xbps_dictionary_get_cstring_nocopy(pkgd, "pkgname", &pkgname);
354
355 xbps_dictionary_get_bool(pkgd, "alternatives-update", &update);
356
357 allkeys = xbps_dictionary_all_keys(pkg_alternatives);
358 for (unsigned int i = 0; i < xbps_array_count(allkeys); i++) {
359 xbps_array_t array;
360 xbps_object_t keysym;
361 bool current = false;
362 const char *first = NULL, *keyname;
363
364 keysym = xbps_array_get(allkeys, i);
365 keyname = xbps_dictionary_keysym_cstring_nocopy(keysym);
366
367 array = xbps_dictionary_get(alternatives, keyname);
368 if (array == NULL)
369 continue;
370
371 xbps_array_get_cstring_nocopy(array, 0, &first);
372 if (strcmp(pkgname, first) == 0) {
373 /* this pkg is the current alternative for this group */
374 current = true;
375 rv = remove_symlinks(xhp,
376 xbps_dictionary_get(pkg_alternatives, keyname),
377 keyname);
378 if (rv != 0)
379 break;
380 }
381
382 if (!update) {
383 xbps_set_cb_state(xhp, XBPS_STATE_ALTGROUP_REMOVED, 0, NULL,
384 "%s: unregistered '%s' alternatives group", pkgver, keyname);
385 xbps_remove_string_from_array(array, pkgname);
386 xbps_array_get_cstring_nocopy(array, 0, &first);
387 }
388
389 if (xbps_array_count(array) == 0) {
390 xbps_dictionary_remove(alternatives, keyname);
391 continue;
392 }
393
394 if (update || !current)
395 continue;
396
397 /* get the new alternative group package */
398 if (switch_alt_group(xhp, keyname, first, &pkg_alternatives) != 0)
399 break;
400 }
401 xbps_object_release(allkeys);
402
403 return rv;
404}
405
406/*
407 * Prune the alternatives group from the db. This will first unregister
408 * it for the package and if there's no other package left providing the
409 * same, also ditch the whole group. When this is called, it is guaranteed
410 * that what is happening is an upgrade, because it's only invoked when
411 * the repo and installed alternatives sets differ for a specific package.
412 */
413static void
414prune_altgroup(struct xbps_handle *xhp, xbps_dictionary_t repod,
415 const char *pkgname, const char *pkgver, const char *keyname)
416{
417 const char *newpkg = NULL, *curpkg = NULL;
418 xbps_array_t array;
419 xbps_dictionary_t alternatives;
420 xbps_string_t kstr;
421 unsigned int grp_count;
422 bool current = false;
423
424 xbps_set_cb_state(xhp, XBPS_STATE_ALTGROUP_REMOVED, 0, NULL,
425 "%s: unregistered '%s' alternatives group", pkgver, keyname);
426
427 alternatives = xbps_dictionary_get(xhp->pkgdb, "_XBPS_ALTERNATIVES_");
428 assert(alternatives);
429 array = xbps_dictionary_get(alternatives, keyname);
430
431 /* if using alt group from another package, we won't switch anything */
432 xbps_array_get_cstring_nocopy(array, 0, &curpkg);
433 current = (strcmp(pkgname, curpkg) == 0);
434
435 /* actually prune the alt group for the current package */
436 xbps_remove_string_from_array(array, pkgname);
437 grp_count = xbps_array_count(array);
438 if (grp_count == 0) {
439 /* it was the last one, ditch the whole thing */
440 xbps_dictionary_remove(alternatives, keyname);
441 return;
442 }
443 if (!current) {
444 /* not the last one, and ours wasn't the one being used */
445 return;
446 }
447
448 if (xbps_array_count(xbps_dictionary_get(repod, "run_depends")) == 0 &&
449 xbps_array_count(xbps_dictionary_get(repod, "shlib-requires")) == 0) {
450 /*
451 * Empty dependencies indicate a removed package (pure meta),
452 * use the first available group after ours has been pruned
453 */
454 xbps_array_get_cstring_nocopy(array, 0, &newpkg);
455 switch_alt_group(xhp, keyname, newpkg, NULL);
456 return;
457 }
458
459 /*
460 * Use the last group, as this indicates that a transitional metapackage
461 * is replacing the original and therefore a new package has registered
462 * a replacement group, which should be last in the array (most recent).
463 */
464 xbps_array_get_cstring_nocopy(array, grp_count - 1, &newpkg);
465
466 /* put the new package as head */
467 kstr = xbps_string_create_cstring(newpkg);
468 xbps_remove_string_from_array(array, newpkg);
469 xbps_array_add_first(array, kstr);
470 xbps_array_get_cstring_nocopy(array, 0, &newpkg);
471 xbps_object_release(kstr);
472
473 switch_alt_group(xhp, keyname, newpkg, NULL);
474}
475
476
477static void
478remove_obsoletes(struct xbps_handle *xhp, const char *pkgname, const char *pkgver,
479 xbps_dictionary_t pkgdb_alts, xbps_dictionary_t repod)
480{
481 xbps_array_t allkeys;
482 xbps_dictionary_t pkgd, pkgd_alts, repod_alts;
483
484 pkgd = xbps_pkgdb_get_pkg(xhp, pkgname);
485 if (xbps_object_type(pkgd) != XBPS_TYPE_DICTIONARY) {
486 return;
487 }
488
489 pkgd_alts = xbps_dictionary_get(pkgd, "alternatives");
490 repod_alts = xbps_dictionary_get(repod, "alternatives");
491
492 if (xbps_object_type(pkgd_alts) != XBPS_TYPE_DICTIONARY) {
493 return;
494 }
495
496 allkeys = xbps_dictionary_all_keys(pkgd_alts);
497 for (unsigned int i = 0; i < xbps_array_count(allkeys); i++) {
498 xbps_array_t array, array2, array_repo;
499 xbps_object_t keysym;
500 const char *keyname, *first = NULL;
501
502 keysym = xbps_array_get(allkeys, i);
503 array = xbps_dictionary_get_keysym(pkgd_alts, keysym);
504 keyname = xbps_dictionary_keysym_cstring_nocopy(keysym);
505
506 array_repo = xbps_dictionary_get(repod_alts, keyname);
507 if (!xbps_array_equals(array, array_repo)) {
508 /*
509 * Check if current provider in pkgdb is this pkg.
510 */
511 array2 = xbps_dictionary_get(pkgdb_alts, keyname);
512 if (array2) {
513 xbps_array_get_cstring_nocopy(array2, 0, &first);
514 if (strcmp(pkgname, first) == 0) {
515 remove_symlinks(xhp, array_repo, keyname);
516 }
517 }
518 }
519 /*
520 * There is nothing left in the alternatives group, which means
521 * the package is being upgraded and is removing it; if we don't
522 * prune it, the system will keep it set after removal of its
523 * parent package, but it will be empty and invalid...
524 */
525 if (xbps_array_count(array_repo) == 0) {
526 prune_altgroup(xhp, repod, pkgname, pkgver, keyname);
527 }
528 }
529 xbps_object_release(allkeys);
530}
531
532int
533xbps_alternatives_register(struct xbps_handle *xhp, xbps_dictionary_t pkg_repod)
534{
535 xbps_array_t allkeys;
536 xbps_dictionary_t alternatives, pkg_alternatives;
537 const char *pkgver, *pkgname;
538 int rv = 0;
539
540 assert(xhp);
541
542 if (xhp->pkgdb == NULL)
543 return EINVAL;
544
545 alternatives = xbps_dictionary_get(xhp->pkgdb, "_XBPS_ALTERNATIVES_");
546 if (alternatives == NULL) {
547 alternatives = xbps_dictionary_create();
548 xbps_dictionary_set(xhp->pkgdb, "_XBPS_ALTERNATIVES_", alternatives);
549 xbps_object_release(alternatives);
550 }
551 alternatives = xbps_dictionary_get(xhp->pkgdb, "_XBPS_ALTERNATIVES_");
552 assert(alternatives);
553
554 xbps_dictionary_get_cstring_nocopy(pkg_repod, "pkgver", &pkgver);
555 xbps_dictionary_get_cstring_nocopy(pkg_repod, "pkgname", &pkgname);
556
557 /*
558 * Compare alternatives from pkgdb and repo and then remove obsolete
559 * symlinks, also remove obsolete (empty) alternatives groups.
560 */
561 remove_obsoletes(xhp, pkgname, pkgver, alternatives, pkg_repod);
562
563 pkg_alternatives = xbps_dictionary_get(pkg_repod, "alternatives");
564 if (!xbps_dictionary_count(pkg_alternatives))
565 return 0;
566
567 allkeys = xbps_dictionary_all_keys(pkg_alternatives);
568 for (unsigned int i = 0; i < xbps_array_count(allkeys); i++) {
569 xbps_array_t array;
570 xbps_object_t keysym;
571 const char *keyname, *first = NULL;
572
573 keysym = xbps_array_get(allkeys, i);
574 keyname = xbps_dictionary_keysym_cstring_nocopy(keysym);
575
576 array = xbps_dictionary_get(alternatives, keyname);
577 if (array == NULL) {
578 array = xbps_array_create();
579 } else {
580 if (xbps_match_string_in_array(array, pkgname)) {
581 xbps_array_get_cstring_nocopy(array, 0, &first);
582 if (strcmp(pkgname, first)) {
583 /* current alternative does not match */
584 continue;
585 }
586 /* already registered, update symlinks */
587 rv = create_symlinks(xhp,
588 xbps_dictionary_get(pkg_alternatives, keyname),
589 keyname);
590 if (rv != 0)
591 break;
592 } else {
593 /* not registered, add provider */
594 xbps_array_add_cstring(array, pkgname);
595 xbps_set_cb_state(xhp, XBPS_STATE_ALTGROUP_ADDED, 0, NULL,
596 "%s: registered '%s' alternatives group", pkgver, keyname);
597 }
598 continue;
599 }
600
601 xbps_array_add_cstring(array, pkgname);
602 xbps_dictionary_set(alternatives, keyname, array);
603 xbps_set_cb_state(xhp, XBPS_STATE_ALTGROUP_ADDED, 0, NULL,
604 "%s: registered '%s' alternatives group", pkgver, keyname);
605 /* apply alternatives for this group */
606 rv = create_symlinks(xhp,
607 xbps_dictionary_get(pkg_alternatives, keyname),
608 keyname);
609 xbps_object_release(array);
610 if (rv != 0)
611 break;
612 }
613 xbps_object_release(allkeys);
614
615 return rv;
616}
int xbps_alternatives_register(struct xbps_handle *xhp, xbps_dictionary_t pkg_repod)
int xbps_alternatives_set(struct xbps_handle *xhp, const char *pkgname, const char *group)
int xbps_alternatives_unregister(struct xbps_handle *xhp, xbps_dictionary_t pkgd)
char rootdir[XBPS_MAXPATH]
Definition xbps.h:650
xbps_dictionary_t pkgdb
Definition xbps.h:576
Generic XBPS structure handler for initialization.
Definition xbps.h:550
xbps_dictionary_t xbps_pkgdb_get_pkg(struct xbps_handle *xhp, const char *pkg)
Definition pkgdb.c:379
bool xbps_match_string_in_array(xbps_array_t array, const char *val)
char * xbps_xasprintf(const char *fmt,...) __attribute__((format(printf
int xbps_mkpath(const char *path, mode_t mode)
Definition mkpath.c:42
bool xbps_remove_string_from_array(xbps_array_t array, const char *str)