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