Branch data Line data Source code
1 : : /*
2 : : * update-alternatives
3 : : *
4 : : * Copyright © 1995 Ian Jackson <ijackson@chiark.greenend.org.uk>
5 : : * Copyright © 2000-2002 Wichert Akkerman <wakkerma@debian.org>
6 : : * Copyright © 2006-2017 Guillem Jover <guillem@debian.org>
7 : : * Copyright © 2008 Pierre Habouzit <madcoder@debian.org>
8 : : * Copyright © 2009-2010 Raphaël Hertzog <hertzog@debian.org>
9 : : *
10 : : * This program is free software; you can redistribute it and/or modify
11 : : * it under the terms of the GNU General Public License as published by
12 : : * the Free Software Foundation; either version 2 of the License, or
13 : : * (at your option) any later version.
14 : : *
15 : : * This program is distributed in the hope that it will be useful,
16 : : * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 : : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 : : * GNU General Public License for more details.
19 : : *
20 : : * You should have received a copy of the GNU General Public License
21 : : * along with this program. If not, see <https://www.gnu.org/licenses/>.
22 : : */
23 : :
24 : : #include <config.h>
25 : : #include <compat.h>
26 : :
27 : : #include <sys/types.h>
28 : : #include <sys/time.h>
29 : : #include <sys/stat.h>
30 : : #include <sys/wait.h>
31 : :
32 : : #include <errno.h>
33 : : #include <stdarg.h>
34 : : #include <stdbool.h>
35 : : #include <stdlib.h>
36 : : #include <stdio.h>
37 : : #include <unistd.h>
38 : : #include <string.h>
39 : : #include <dirent.h>
40 : : #include <time.h>
41 : : #include <setjmp.h>
42 : : #include <assert.h>
43 : : #include <locale.h>
44 : : #include <ctype.h>
45 : : #include <limits.h>
46 : :
47 : : #include <dpkg/macros.h>
48 : : #include <dpkg/i18n.h>
49 : :
50 : : /* Global variables: */
51 : :
52 : : #define PROGNAME "update-alternatives"
53 : :
54 : : static const char *altdir = SYSCONFDIR "/alternatives";
55 : : static char *admdir = NULL;
56 : : static const char *instdir = "";
57 : : static size_t instdir_len;
58 : :
59 : : static const char *prog_path = "update-alternatives";
60 : :
61 : : enum action {
62 : : ACTION_NONE,
63 : : ACTION_INSTALL,
64 : : ACTION_SET,
65 : : ACTION_SET_SELECTIONS,
66 : : ACTION_GET_SELECTIONS,
67 : : ACTION_AUTO,
68 : : ACTION_CONFIG,
69 : : ACTION_CONFIG_ALL,
70 : : ACTION_REMOVE,
71 : : ACTION_REMOVE_ALL,
72 : : ACTION_LIST,
73 : : ACTION_QUERY,
74 : : ACTION_DISPLAY,
75 : : };
76 : :
77 : : static struct action_name {
78 : : enum action action;
79 : : const char *name;
80 : : } action_names[] = {
81 : : { ACTION_NONE, "" },
82 : : { ACTION_INSTALL, "install" },
83 : : { ACTION_SET, "set" },
84 : : { ACTION_SET_SELECTIONS, "set-selections" },
85 : : { ACTION_GET_SELECTIONS, "get-selections" },
86 : : { ACTION_AUTO, "auto" },
87 : : { ACTION_CONFIG, "config" },
88 : : { ACTION_CONFIG_ALL, "all" },
89 : : { ACTION_REMOVE, "remove" },
90 : : { ACTION_REMOVE_ALL, "remove-all" },
91 : : { ACTION_LIST, "list" },
92 : : { ACTION_QUERY, "query" },
93 : : { ACTION_DISPLAY, "display" },
94 : : };
95 : :
96 : : enum output_mode {
97 : : OUTPUT_QUIET = -1,
98 : : OUTPUT_NORMAL = 0,
99 : : OUTPUT_VERBOSE = 1,
100 : : OUTPUT_DEBUG = 2,
101 : : };
102 : :
103 : : /* Action to perform */
104 : : static enum action action = ACTION_NONE;
105 : : static char *log_file = NULL;
106 : : static FILE *fh_log = NULL;
107 : : /* Skip alternatives properly configured in auto mode (for --config) */
108 : : static int opt_skip_auto = 0;
109 : : static int opt_verbose = OUTPUT_NORMAL;
110 : : static int opt_force = 0;
111 : :
112 : : /*
113 : : * Functions.
114 : : */
115 : :
116 : : static void
117 : 0 : version(void)
118 : : {
119 : 0 : printf(_("%s version %s.\n"), PROGNAME, VERSION);
120 : 0 : printf("\n");
121 : :
122 : 0 : printf(_(
123 : : "This is free software; see the GNU General Public License version 2 or\n"
124 : : "later for copying conditions. There is NO warranty.\n"));
125 : 0 : }
126 : :
127 : : static void
128 : 0 : usage(void)
129 : : {
130 : 0 : printf(_(
131 : : "Usage: %s [<option> ...] <command>\n"
132 : : "\n"), PROGNAME);
133 : :
134 : 0 : printf(_(
135 : : "Commands:\n"
136 : : " --install <link> <name> <path> <priority>\n"
137 : : " [--slave <link> <name> <path>] ...\n"
138 : : " add a group of alternatives to the system.\n"
139 : : " --remove <name> <path> remove <path> from the <name> group alternative.\n"
140 : : " --remove-all <name> remove <name> group from the alternatives system.\n"
141 : : " --auto <name> switch the master link <name> to automatic mode.\n"
142 : : " --display <name> display information about the <name> group.\n"
143 : : " --query <name> machine parseable version of --display <name>.\n"
144 : : " --list <name> display all targets of the <name> group.\n"
145 : : " --get-selections list master alternative names and their status.\n"
146 : : " --set-selections read alternative status from standard input.\n"
147 : : " --config <name> show alternatives for the <name> group and ask the\n"
148 : : " user to select which one to use.\n"
149 : : " --set <name> <path> set <path> as alternative for <name>.\n"
150 : : " --all call --config on all alternatives.\n"
151 : : "\n"));
152 : :
153 : 0 : printf(_(
154 : : "<link> is the symlink pointing to %s/<name>.\n"
155 : : " (e.g. /usr/bin/pager)\n"
156 : : "<name> is the master name for this link group.\n"
157 : : " (e.g. pager)\n"
158 : : "<path> is the location of one of the alternative target files.\n"
159 : : " (e.g. /usr/bin/less)\n"
160 : : "<priority> is an integer; options with higher numbers have higher priority in\n"
161 : : " automatic mode.\n"
162 : : "\n"), altdir);
163 : :
164 : 0 : printf(_(
165 : : "Options:\n"
166 : : " --altdir <directory> change the alternatives directory\n"
167 : : " (default is %s).\n"
168 : : " --admindir <directory> change the administrative directory\n"
169 : : " (default is %s).\n"
170 : : " --instdir <directory> change the installation directory.\n"
171 : : " --root <directory> change the filesystem root directory.\n"
172 : : " --log <file> change the log file.\n"
173 : : " --force allow replacing files with alternative links.\n"
174 : : " --skip-auto skip prompt for alternatives correctly configured\n"
175 : : " in automatic mode (relevant for --config only)\n"
176 : : " --quiet quiet operation, minimal output.\n"
177 : : " --verbose verbose operation, more output.\n"
178 : : " --debug debug output, way more output.\n"
179 : : " --help show this help message.\n"
180 : : " --version show the version.\n"
181 : : ), altdir, admdir);
182 : 0 : }
183 : :
184 : : static void LIBCOMPAT_ATTR_NORET LIBCOMPAT_ATTR_PRINTF(1)
185 : 14 : error(char const *fmt, ...)
186 : : {
187 : : va_list args;
188 : :
189 : 14 : fprintf(stderr, "%s: %s: ", PROGNAME, _("error"));
190 : 14 : va_start(args, fmt);
191 : 14 : vfprintf(stderr, fmt, args);
192 : 14 : va_end(args);
193 : 14 : fprintf(stderr, "\n");
194 : 14 : exit(2);
195 : : }
196 : :
197 : : static void LIBCOMPAT_ATTR_NORET LIBCOMPAT_ATTR_PRINTF(1)
198 : 2 : syserr(char const *fmt, ...)
199 : : {
200 : : va_list args;
201 : :
202 : 2 : fprintf(stderr, "%s: %s: ", PROGNAME, _("error"));
203 : 2 : va_start(args, fmt);
204 : 2 : vfprintf(stderr, fmt, args);
205 : 2 : va_end(args);
206 : 2 : fprintf(stderr, ": %s\n", strerror(errno));
207 : 2 : exit(2);
208 : : }
209 : :
210 : : static void LIBCOMPAT_ATTR_NORET LIBCOMPAT_ATTR_PRINTF(1)
211 : 1 : badusage(char const *fmt, ...)
212 : : {
213 : : va_list args;
214 : :
215 : 1 : fprintf(stderr, "%s: ", PROGNAME);
216 : 1 : va_start(args, fmt);
217 : 1 : vfprintf(stderr, fmt, args);
218 : 1 : va_end(args);
219 : 1 : fprintf(stderr, "\n\n");
220 : 1 : fprintf(stderr, _("Use '%s --help' for program usage information."),
221 : : PROGNAME);
222 : 1 : fprintf(stderr, "\n");
223 : 1 : exit(2);
224 : : }
225 : :
226 : : static void LIBCOMPAT_ATTR_PRINTF(1)
227 : 28 : warning(char const *fmt, ...)
228 : : {
229 : : va_list args;
230 : :
231 [ + - ]: 28 : if (opt_verbose < OUTPUT_NORMAL)
232 : 28 : return;
233 : :
234 : 0 : fprintf(stderr, "%s: %s: ", PROGNAME, _("warning"));
235 : 0 : va_start(args, fmt);
236 : 0 : vfprintf(stderr, fmt, args);
237 : 0 : va_end(args);
238 : 0 : fprintf(stderr, "\n");
239 : : }
240 : :
241 : : static void LIBCOMPAT_ATTR_PRINTF(1)
242 : 184 : debug(char const *fmt, ...)
243 : : {
244 : : va_list args;
245 : :
246 [ + + ]: 184 : if (opt_verbose < OUTPUT_DEBUG)
247 : 169 : return;
248 : :
249 : 15 : fprintf(stderr, "DEBUG: ");
250 : 15 : va_start(args, fmt);
251 : 15 : vfprintf(stderr, fmt, args);
252 : 15 : va_end(args);
253 : 15 : fprintf(stderr, "\n");
254 : : }
255 : :
256 : : static void LIBCOMPAT_ATTR_PRINTF(1)
257 : 31 : verbose(char const *fmt, ...)
258 : : {
259 : : va_list args;
260 : :
261 [ + - ]: 31 : if (opt_verbose < OUTPUT_VERBOSE)
262 : 31 : return;
263 : :
264 : 0 : printf("%s: ", PROGNAME);
265 : 0 : va_start(args, fmt);
266 : 0 : vprintf(fmt, args);
267 : 0 : va_end(args);
268 : 0 : printf("\n");
269 : : }
270 : :
271 : : static void LIBCOMPAT_ATTR_PRINTF(1)
272 : 41 : info(char const *fmt, ...)
273 : : {
274 : : va_list args;
275 : :
276 [ + - ]: 41 : if (opt_verbose < OUTPUT_NORMAL)
277 : 41 : return;
278 : :
279 : 0 : printf("%s: ", PROGNAME);
280 : 0 : va_start(args, fmt);
281 : 0 : vprintf(fmt, args);
282 : 0 : va_end(args);
283 : 0 : printf("\n");
284 : : }
285 : :
286 : : static void LIBCOMPAT_ATTR_PRINTF(1)
287 : 440 : pr(char const *fmt, ...)
288 : : {
289 : : va_list args;
290 : :
291 : 440 : va_start(args, fmt);
292 : 440 : vprintf(fmt, args);
293 : 440 : va_end(args);
294 : 440 : printf("\n");
295 : 440 : }
296 : :
297 : : static void *
298 : 3923 : xmalloc(size_t size)
299 : : {
300 : : void *ptr;
301 : :
302 : 3923 : ptr = malloc(size);
303 [ - + ]: 3923 : if (!ptr)
304 : 0 : error(_("malloc failed (%zu bytes)"), size);
305 : :
306 : 3923 : return ptr;
307 : : }
308 : :
309 : : static char *
310 : 3417 : xstrdup(const char *str)
311 : : {
312 : : char *new_str;
313 : :
314 [ + + ]: 3417 : if (!str)
315 : 60 : return NULL;
316 : :
317 : 3357 : new_str = strdup(str);
318 [ - + ]: 3357 : if (!new_str)
319 : 0 : error(_("failed to allocate memory"));
320 : :
321 : 3357 : return new_str;
322 : : }
323 : :
324 : : static char *
325 : 0 : xstrndup(const char *str, size_t n)
326 : : {
327 : : char *new_str;
328 : :
329 [ # # ]: 0 : if (!str)
330 : 0 : return NULL;
331 : :
332 : 0 : new_str = strndup(str, n);
333 [ # # ]: 0 : if (!new_str)
334 : 0 : error(_("failed to allocate memory"));
335 : :
336 : 0 : return new_str;
337 : : }
338 : :
339 : : static char * LIBCOMPAT_ATTR_VPRINTF(1)
340 : 3230 : xvasprintf(const char *fmt, va_list args)
341 : : {
342 : : char *str;
343 : :
344 [ - + ]: 3230 : if (vasprintf(&str, fmt, args) < 0)
345 : 0 : error(_("failed to allocate memory"));
346 : :
347 : 3230 : return str;
348 : : }
349 : :
350 : : static char * LIBCOMPAT_ATTR_PRINTF(1)
351 : 3166 : xasprintf(const char *fmt, ...)
352 : : {
353 : : va_list args;
354 : : char *str;
355 : :
356 : 3166 : va_start(args, fmt);
357 : 3166 : str = xvasprintf(fmt, args);
358 : 3166 : va_end(args);
359 : :
360 : 3166 : return str;
361 : : }
362 : :
363 : : static char *
364 : 225 : areadlink(const char *linkname)
365 : : {
366 : : struct stat st;
367 : : char *buf;
368 : : ssize_t size;
369 : :
370 : : /* Allocate required memory to store the value of the symlink */
371 [ + + ]: 225 : if (lstat(linkname, &st))
372 : 36 : return NULL;
373 : :
374 [ + + ]: 189 : if (!S_ISLNK(st.st_mode)) {
375 : 2 : errno = EINVAL;
376 : 2 : return NULL;
377 : : }
378 : :
379 : 187 : buf = xmalloc(st.st_size + 1);
380 : :
381 : : /* Read it and terminate the string properly */
382 : 187 : size = readlink(linkname, buf, st.st_size);
383 [ - + ]: 187 : if (size < 0) {
384 : 0 : int saved_errno = errno;
385 : :
386 : 0 : free(buf);
387 : 0 : errno = saved_errno;
388 : :
389 : 0 : return NULL;
390 : : }
391 : 187 : buf[size] = '\0';
392 : :
393 : 187 : return buf;
394 : : }
395 : :
396 : : static int
397 : 0 : spawn(const char *prog, const char *args[])
398 : : {
399 : : pid_t pid, dead_pid;
400 : : int status;
401 : :
402 : 0 : pid = fork();
403 [ # # ]: 0 : if (pid < 0)
404 : 0 : error(_("fork failed"));
405 [ # # ]: 0 : if (pid == 0) {
406 : 0 : execvp(prog, (char *const *)args);
407 : 0 : syserr(_("unable to execute %s (%s)"), prog, prog);
408 : : }
409 [ # # # # ]: 0 : while ((dead_pid = waitpid(pid, &status, 0)) < 0 && errno == EINTR) ;
410 [ # # ]: 0 : if (dead_pid != pid)
411 : 0 : error(_("wait for subprocess %s failed"), prog);
412 : :
413 : 0 : return status;
414 : : }
415 : :
416 : : static bool
417 : 237 : rename_mv(const char *src, const char *dst)
418 : : {
419 : 237 : const char *args[] = { "mv", src, dst, NULL };
420 : : int rc;
421 : :
422 [ + - ]: 237 : if (rename(src, dst) == 0)
423 : 237 : return true;
424 [ # # ]: 0 : if (errno == ENOENT)
425 : 0 : return false;
426 : :
427 : 0 : rc = spawn("mv", args);
428 [ # # # # ]: 0 : if (WIFEXITED(rc) && WEXITSTATUS(rc) == 0)
429 : 0 : return true;
430 : :
431 : 0 : return false;
432 : : }
433 : :
434 : : static void
435 : 237 : xrename(const char *src, const char *dst)
436 : : {
437 [ - + ]: 237 : if (!rename_mv(src, dst))
438 : 0 : syserr(_("unable to install '%.250s' as '%.250s'"), src, dst);
439 : 237 : }
440 : :
441 : : static void LIBCOMPAT_ATTR_PRINTF(1)
442 : 5 : xunlink_args(const char *fmt, ...)
443 : : {
444 : : va_list args;
445 : : char *path;
446 : :
447 : 5 : va_start(args, fmt);
448 : 5 : path = xvasprintf(fmt, args);
449 : 5 : va_end(args);
450 : :
451 [ - + - - ]: 5 : if (unlink(path) < 0 && errno != ENOENT)
452 : 0 : syserr(_("unable to remove '%s'"), path);
453 : :
454 : 5 : free(path);
455 : 5 : }
456 : :
457 : : static char *
458 : 0 : xdirname(const char *pathname)
459 : : {
460 : : char *dirname, *slash;
461 : :
462 : 0 : slash = strrchr(pathname, '/');
463 [ # # ]: 0 : if (slash)
464 : 0 : dirname = xstrndup(pathname, slash - pathname);
465 : : else
466 : 0 : dirname = xstrdup(".");
467 : :
468 : 0 : return dirname;
469 : : }
470 : :
471 : : static int
472 : 0 : make_path(const char *pathname, mode_t mode)
473 : : {
474 : : char *dirname, *slash;
475 : :
476 : 0 : dirname = xstrdup(pathname);
477 : :
478 : : /* Find the first slash, and ignore it, as it will be either the
479 : : * slash for the root directory, for the current directory in a
480 : : * relative pathname or its parent. */
481 : 0 : slash = strchr(dirname, '/');
482 : :
483 [ # # ]: 0 : while (slash != NULL) {
484 : 0 : slash = strchr(slash + 1, '/');
485 [ # # ]: 0 : if (slash)
486 : 0 : *slash = '\0';
487 : :
488 [ # # # # ]: 0 : if (mkdir(dirname, mode) < 0 && errno != EEXIST) {
489 : 0 : free(dirname);
490 : 0 : return -1;
491 : : }
492 [ # # ]: 0 : if (slash)
493 : 0 : *slash = '/';
494 : : }
495 : :
496 : 0 : free(dirname);
497 : :
498 : 0 : return 0;
499 : : }
500 : :
501 : : static void LIBCOMPAT_ATTR_PRINTF(1)
502 : 107 : log_msg(const char *fmt, ...)
503 : : {
504 [ + + ]: 107 : if (fh_log == NULL) {
505 : 53 : fh_log = fopen(log_file, "a");
506 [ - + - - ]: 53 : if (fh_log == NULL && errno == ENOENT) {
507 : 0 : char *log_dir = xdirname(log_file);
508 : :
509 [ # # ]: 0 : if (make_path(log_dir, 0755) < 0)
510 : 0 : syserr(_("cannot create log directory '%s'"),
511 : : log_dir);
512 : 0 : free(log_dir);
513 : :
514 : 0 : fh_log = fopen(log_file, "a");
515 : : }
516 [ - + - - ]: 53 : if (fh_log == NULL && errno != EACCES)
517 : 0 : syserr(_("cannot append to '%s'"), log_file);
518 : : }
519 : :
520 [ + - ]: 107 : if (fh_log) {
521 : : va_list args;
522 : : char timestamp[64];
523 : : time_t now;
524 : : struct tm tm;
525 : :
526 : 107 : time(&now);
527 [ - + ]: 107 : if (localtime_r(&now, &tm) == NULL)
528 : 0 : syserr(_("cannot get local time to log into '%s'"), log_file);
529 : 107 : strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S",
530 : : &tm);
531 : 107 : fprintf(fh_log, "%s %s: ", PROGNAME, timestamp);
532 : 107 : va_start(args, fmt);
533 : 107 : vfprintf(fh_log, fmt, args);
534 : 107 : va_end(args);
535 : 107 : fprintf(fh_log, "\n");
536 : : }
537 : 107 : }
538 : :
539 : : /*
540 : : * Filesystem access for alternative handling.
541 : : */
542 : :
543 : : static char *
544 : 2370 : fsys_get_path(const char *pathpart)
545 : : {
546 : 2370 : return xasprintf("%s%s", instdir, pathpart);
547 : : }
548 : :
549 : : static const char *
550 : 116 : fsys_set_dir(const char *dir)
551 : : {
552 [ + + ]: 116 : if (dir == NULL) {
553 : : const char *instdir_env;
554 : :
555 : 107 : instdir_env = getenv(INSTDIR_ENVVAR);
556 [ + + ]: 107 : if (instdir_env)
557 : 10 : dir = instdir_env;
558 : : else
559 : 97 : dir = "";
560 : : }
561 : :
562 : 116 : instdir_len = strlen(dir);
563 : :
564 : 116 : return dir;
565 : : }
566 : :
567 : : static char *
568 : 106 : fsys_gen_admindir(void)
569 : : {
570 : 106 : return fsys_get_path(ADMINDIR "/alternatives");
571 : : }
572 : :
573 : : static bool
574 : 626 : fsys_pathname_is_missing(const char *pathname)
575 : : {
576 : : struct stat st;
577 : : char *root_pathname;
578 : :
579 : 626 : root_pathname = fsys_get_path(pathname);
580 : :
581 : 626 : errno = 0;
582 [ + + - + ]: 626 : if (stat(root_pathname, &st) < 0 && errno != ENOENT)
583 : 0 : syserr(_("cannot stat file '%s'"), root_pathname);
584 : :
585 : 626 : free(root_pathname);
586 : :
587 [ + + ]: 626 : if (errno == ENOENT)
588 : 153 : return true;
589 : :
590 : 473 : return false;
591 : : }
592 : :
593 : : static int
594 : 337 : fsys_lstat(const char *linkname, struct stat *st)
595 : : {
596 : : char *root_linkname;
597 : : int rc;
598 : :
599 : 337 : root_linkname = fsys_get_path(linkname);
600 : :
601 : 337 : errno = 0;
602 : 337 : rc = lstat(root_linkname, st);
603 : :
604 : 337 : free(root_linkname);
605 : :
606 : 337 : return rc;
607 : : }
608 : :
609 : : static char *
610 : 225 : fsys_areadlink(const char *linkname)
611 : : {
612 : : char *root_linkname;
613 : : char *target;
614 : :
615 : 225 : root_linkname = fsys_get_path(linkname);
616 : 225 : target = areadlink(root_linkname);
617 : 225 : free(root_linkname);
618 : :
619 : 225 : return target;
620 : : }
621 : :
622 : : static char *
623 : 32 : fsys_xreadlink(const char *linkname)
624 : : {
625 : : char *buf;
626 : :
627 : 32 : buf = fsys_areadlink(linkname);
628 [ - + ]: 32 : if (buf == NULL)
629 : 0 : syserr(_("unable to read link '%s%.255s'"), instdir, linkname);
630 : :
631 : 32 : return buf;
632 : : }
633 : :
634 : : static void
635 : 139 : fsys_set_ref_time(const char *linkname, const char *target)
636 : : {
637 : : #ifdef HAVE_LUTIMES
638 : : /* If the symlink did not exist, then copy the timestamps
639 : : * from the target. This is needed so we can get reproducible
640 : : * installations, for programs that track these timestamps on
641 : : * their databases. */
642 : : struct stat st;
643 : : struct timeval tv[2];
644 : : char *root_linkname;
645 : :
646 [ - + ]: 139 : if (fsys_lstat(target, &st) < 0) {
647 [ # # ]: 0 : if (errno != ENOENT)
648 : 0 : syserr(_("unable to get file '%s%s' metadata"),
649 : : instdir, target);
650 : 0 : return;
651 : : }
652 : :
653 : 139 : tv[0].tv_sec = st.st_mtime;
654 : 139 : tv[0].tv_usec = 0;
655 : 139 : tv[1].tv_sec = st.st_mtime;
656 : 139 : tv[1].tv_usec = 0;
657 : :
658 : 139 : root_linkname = fsys_get_path(linkname);
659 [ - + - - ]: 139 : if (lutimes(root_linkname, tv) < 0 && errno != ENOSYS)
660 : 0 : syserr(_("cannot set symlink '%s' timestamp"), root_linkname);
661 : 139 : free(root_linkname);
662 : : #endif
663 : : }
664 : :
665 : : static void
666 : 194 : fsys_symlink(const char *filename, const char *linkname)
667 : : {
668 : : char *root_linkname;
669 : :
670 : 194 : root_linkname = fsys_get_path(linkname);
671 : :
672 [ + + - + ]: 194 : if (unlink(root_linkname) < 0 && errno != ENOENT)
673 : 0 : syserr(_("unable to remove '%s'"), root_linkname);
674 : :
675 [ + + ]: 194 : if (symlink(filename, root_linkname))
676 : 2 : syserr(_("error creating symbolic link '%.255s'"), root_linkname);
677 : :
678 : 192 : free(root_linkname);
679 : 192 : }
680 : :
681 : : static void
682 : 192 : fsys_mv(const char *src, const char *dst)
683 : : {
684 : : char *root_src;
685 : : char *root_dst;
686 : :
687 : 192 : root_src = fsys_get_path(src);
688 : 192 : root_dst = fsys_get_path(dst);
689 : :
690 : 192 : xrename(root_src, root_dst);
691 : :
692 : 192 : free(root_src);
693 : 192 : free(root_dst);
694 : 192 : }
695 : :
696 : : static void
697 : 136 : fsys_rm(const char *f)
698 : : {
699 : : char *root_f;
700 : :
701 : 136 : root_f = fsys_get_path(f);
702 : :
703 [ + + - + ]: 136 : if (unlink(root_f) < 0 && errno != ENOENT)
704 : 0 : syserr(_("unable to remove '%s'"), root_f);
705 : :
706 : 136 : free(root_f);
707 : 136 : }
708 : :
709 : : static void LIBCOMPAT_ATTR_PRINTF(1)
710 : 57 : fsys_rm_args(const char *fmt, ...)
711 : : {
712 : : va_list args;
713 : : char *path;
714 : :
715 : 57 : va_start(args, fmt);
716 : 57 : path = xvasprintf(fmt, args);
717 : 57 : va_end(args);
718 : :
719 : 57 : fsys_rm(path);
720 : 57 : free(path);
721 : 57 : }
722 : :
723 : : /*
724 : : * OBJECTS
725 : : */
726 : :
727 : : struct fileset {
728 : : struct fileset *next;
729 : :
730 : : char *master_file;
731 : : int priority;
732 : :
733 : : struct slave_file {
734 : : struct slave_file *next;
735 : : char *name;
736 : : char *file;
737 : : } *slaves;
738 : : };
739 : :
740 : : static struct fileset *
741 : 186 : fileset_new(const char *master_file, int prio)
742 : : {
743 : : struct fileset *fs;
744 : :
745 : 186 : fs = xmalloc(sizeof(*fs));
746 : 186 : fs->next = NULL;
747 : 186 : fs->master_file = xstrdup(master_file);
748 : 186 : fs->priority = prio;
749 : 186 : fs->slaves = NULL;
750 : :
751 : 186 : return fs;
752 : : }
753 : :
754 : : static void
755 : 165 : fileset_free(struct fileset *fs)
756 : : {
757 : : struct slave_file *slave, *next;
758 : :
759 : 165 : free(fs->master_file);
760 [ + + ]: 706 : for (slave = fs->slaves; slave; slave = next) {
761 : 541 : next = slave->next;
762 : 541 : free(slave->name);
763 : 541 : free(slave->file);
764 : 541 : free(slave);
765 : : }
766 : 165 : free(fs);
767 : 165 : }
768 : :
769 : : static void
770 : 579 : fileset_add_slave(struct fileset *fs, const char *name, const char *file)
771 : : {
772 : 579 : struct slave_file *sl, *cur, *prev = NULL;
773 : :
774 : : /* Replace existing first */
775 [ + + ]: 1380 : for (cur = fs->slaves; cur; cur = cur->next) {
776 [ - + ]: 801 : if (strcmp(cur->name, name) == 0) {
777 : 0 : free(cur->file);
778 : 0 : cur->file = xstrdup(file);
779 : 0 : return;
780 : : }
781 : 801 : prev = cur;
782 : : }
783 : :
784 : : /* Otherwise add new at the end */
785 : 579 : sl = xmalloc(sizeof(*sl));
786 : 579 : sl->next = NULL;
787 : 579 : sl->name = xstrdup(name);
788 : 579 : sl->file = xstrdup(file);
789 [ + + ]: 579 : if (prev)
790 : 402 : prev->next = sl;
791 : : else
792 : 177 : fs->slaves = sl;
793 : : }
794 : :
795 : : static const char *
796 : 1439 : fileset_get_slave(struct fileset *fs, const char *name)
797 : : {
798 : : struct slave_file *slave;
799 : :
800 [ + + ]: 3337 : for (slave = fs->slaves; slave; slave = slave->next) {
801 [ + + ]: 3280 : if (strcmp(slave->name, name) == 0)
802 : 1382 : return slave->file;
803 : : }
804 : :
805 : 57 : return NULL;
806 : : }
807 : :
808 : : static bool
809 : 815 : fileset_has_slave(struct fileset *fs, const char *name)
810 : : {
811 : 815 : const char *file = fileset_get_slave(fs, name);
812 : :
813 [ + + ]: 815 : if (file == NULL)
814 : 57 : return false;
815 : :
816 : 758 : return file[0] != '\0';
817 : : }
818 : :
819 : : static bool
820 : 163 : fileset_can_install_slave(struct fileset *fs, const char *slave_name)
821 : : {
822 : : /* Decide whether the slave alternative must be setup */
823 [ + + ]: 163 : if (fileset_has_slave(fs, slave_name)) {
824 : 130 : const char *slave = fileset_get_slave(fs, slave_name);
825 : :
826 [ + + ]: 130 : if (!fsys_pathname_is_missing(slave))
827 : 122 : return true;
828 : : }
829 : :
830 : 41 : return false;
831 : : }
832 : :
833 : : struct slave_link {
834 : : struct slave_link *next;
835 : : char *name;
836 : : char *link;
837 : : bool updated;
838 : : };
839 : :
840 : : struct commit_operation {
841 : : struct commit_operation *next;
842 : :
843 : : enum opcode {
844 : : OPCODE_NOP,
845 : : OPCODE_RM,
846 : : OPCODE_MV,
847 : : OPCODE_REF_TIME,
848 : : } opcode;
849 : :
850 : : char *arg_a;
851 : : char *arg_b;
852 : : };
853 : :
854 : : enum alternative_update_reason {
855 : : ALT_UPDATE_NO,
856 : : ALT_UPDATE_SLAVE_CHANGED,
857 : : ALT_UPDATE_LINK_BROKEN,
858 : : };
859 : :
860 : : struct alternative {
861 : : char *master_name;
862 : : char *master_link;
863 : : char *current;
864 : :
865 : : enum alternative_status {
866 : : ALT_ST_UNKNOWN,
867 : : ALT_ST_AUTO,
868 : : ALT_ST_MANUAL,
869 : : } status;
870 : :
871 : : struct slave_link *slaves;
872 : : struct fileset *choices;
873 : :
874 : : struct commit_operation *commit_ops;
875 : :
876 : : int ref_count;
877 : : bool modified;
878 : : bool known_current;
879 : : };
880 : :
881 : : static void
882 : 347 : slave_link_free(struct slave_link *slave)
883 : : {
884 : 347 : free(slave->name);
885 : 347 : free(slave->link);
886 : 347 : free(slave);
887 : 347 : }
888 : :
889 : : static void
890 : 387 : commit_operation_free(struct commit_operation *commit_op)
891 : : {
892 : 387 : free(commit_op->arg_a);
893 : 387 : free(commit_op->arg_b);
894 : 387 : free(commit_op);
895 : 387 : }
896 : :
897 : : static struct alternative *
898 : 166 : alternative_new(const char *name)
899 : : {
900 : : struct alternative *alt;
901 : :
902 : 166 : alt = xmalloc(sizeof(*alt));
903 : 166 : alt->master_name = xstrdup(name);
904 : 166 : alt->master_link = NULL;
905 : 166 : alt->current = NULL;
906 : 166 : alt->status = ALT_ST_UNKNOWN;
907 : 166 : alt->slaves = NULL;
908 : 166 : alt->choices = NULL;
909 : 166 : alt->commit_ops = NULL;
910 : 166 : alt->modified = false;
911 : 166 : alt->known_current = false;
912 : 166 : alt->ref_count = 1;
913 : :
914 : 166 : return alt;
915 : : }
916 : :
917 : : static inline void
918 : 234 : alternative_ref(struct alternative *a)
919 : : {
920 : 234 : a->ref_count++;
921 : 234 : }
922 : :
923 : : static inline bool
924 : 301 : alternative_unref(struct alternative *a)
925 : : {
926 : 301 : return --a->ref_count == 0;
927 : : }
928 : :
929 : : static void
930 : 225 : alternative_choices_free(struct alternative *a)
931 : : {
932 : : struct fileset *fs;
933 : :
934 [ + + ]: 225 : if (a->choices)
935 : 91 : a->modified = true;
936 : :
937 [ + + ]: 373 : while (a->choices) {
938 : 148 : fs = a->choices;
939 : 148 : a->choices = fs->next;
940 : 148 : fileset_free(fs);
941 : : }
942 : 225 : }
943 : :
944 : : static void
945 : 269 : alternative_commit_operations_free(struct alternative *a)
946 : : {
947 : : struct commit_operation *op;
948 : :
949 [ + + ]: 656 : while (a->commit_ops) {
950 : 387 : op = a->commit_ops;
951 : 387 : a->commit_ops = op->next;
952 : 387 : commit_operation_free(op);
953 : : }
954 : 269 : }
955 : :
956 : : static void
957 : 223 : alternative_reset(struct alternative *alt)
958 : : {
959 : : struct slave_link *slave;
960 : :
961 : 223 : free(alt->current);
962 : 223 : alt->current = NULL;
963 : 223 : free(alt->master_link);
964 : 223 : alt->master_link = NULL;
965 [ + + ]: 562 : while (alt->slaves) {
966 : 339 : slave = alt->slaves;
967 : 339 : alt->slaves = slave->next;
968 : 339 : slave_link_free(slave);
969 : : }
970 : 223 : alternative_choices_free(alt);
971 : 223 : alternative_commit_operations_free(alt);
972 : 223 : alt->modified = false;
973 : 223 : alt->known_current = false;
974 : 223 : }
975 : :
976 : : static void
977 : 272 : alternative_free(struct alternative *alt)
978 : : {
979 [ + + ]: 272 : if (!alternative_unref(alt))
980 : 142 : return;
981 : :
982 : 130 : alternative_reset(alt);
983 : 130 : free(alt->master_name);
984 : 130 : free(alt);
985 : : }
986 : :
987 : : static int
988 : 105 : alternative_choices_count(struct alternative *alt)
989 : : {
990 : : struct fileset *fs;
991 : 105 : int count = 0;
992 : :
993 [ + + ]: 270 : for (fs = alt->choices; fs; fs = fs->next)
994 : 165 : count++;
995 : :
996 : 105 : return count;
997 : : }
998 : :
999 : : static int
1000 : 109 : alternative_slaves_count(struct alternative *alt)
1001 : : {
1002 : : struct slave_link *sl;
1003 : 109 : int count = 0;
1004 : :
1005 [ + + ]: 472 : for (sl = alt->slaves; sl; sl = sl->next)
1006 : 363 : count++;
1007 : :
1008 : 109 : return count;
1009 : : }
1010 : :
1011 : : static int
1012 : 28 : compare_fileset(const void *va, const void *vb)
1013 : : {
1014 : 28 : const struct fileset *a = *(const struct fileset **)va;
1015 : 28 : const struct fileset *b = *(const struct fileset **)vb;
1016 : :
1017 [ + - + - ]: 28 : assert(a && a->master_file);
1018 [ + - + - ]: 28 : assert(b && b->master_file);
1019 : :
1020 : 28 : return strcmp(a->master_file, b->master_file);
1021 : : }
1022 : :
1023 : : static int
1024 : 143 : compare_slave_link(const void *va, const void *vb)
1025 : : {
1026 : 143 : const struct slave_link *a = *(const struct slave_link **)va;
1027 : 143 : const struct slave_link *b = *(const struct slave_link **)vb;
1028 : :
1029 [ + - + - ]: 143 : assert(a && a->name);
1030 [ + - + - ]: 143 : assert(b && b->name);
1031 : :
1032 : 143 : return strcmp(a->name, b->name);
1033 : : }
1034 : :
1035 : : static void
1036 : 45 : alternative_sort_choices(struct alternative *a)
1037 : : {
1038 : : int count, i;
1039 : : struct fileset **table, *fs;
1040 : :
1041 : 45 : count = alternative_choices_count(a);
1042 [ + + ]: 45 : if (count < 2) /* Nothing to sort */
1043 : 22 : return;
1044 : :
1045 : : /* Store objects in a table instead of a linked list */
1046 : 23 : table = xmalloc(sizeof(fs) * count);
1047 [ + + ]: 74 : for (fs = a->choices, i = 0; fs; fs = fs->next) {
1048 [ - + ]: 51 : assert(fs->master_file);
1049 : 51 : table[i++] = fs;
1050 : : }
1051 : :
1052 : 23 : qsort(table, count, sizeof(fs), compare_fileset);
1053 : :
1054 : : /* Rewrite the linked list from the sorted table */
1055 : 23 : a->choices = fs = table[0];
1056 : 23 : table[count - 1]->next = NULL;
1057 [ + + ]: 51 : for (i = 1; i < count; fs = fs->next, i++)
1058 : 28 : fs->next = table[i];
1059 : 23 : free(table);
1060 : : }
1061 : :
1062 : : static void
1063 : 45 : alternative_sort_slaves(struct alternative *a)
1064 : : {
1065 : : int count, i;
1066 : : struct slave_link **table, *sl;
1067 : :
1068 : 45 : count = alternative_slaves_count(a);
1069 [ + + ]: 45 : if (count < 2) /* Nothing to sort */
1070 : 11 : return;
1071 : :
1072 : : /* Store objects in a table instead of a linked list */
1073 : 34 : table = xmalloc(sizeof(sl) * count);
1074 [ + + ]: 169 : for (sl = a->slaves, i = 0; sl; sl = sl->next, i++) {
1075 : 135 : table[i] = sl;
1076 : : }
1077 : :
1078 : 34 : qsort(table, count, sizeof(sl), compare_slave_link);
1079 : :
1080 : : /* Rewrite the linked list from the sorted table */
1081 : 34 : a->slaves = sl = table[0];
1082 : 34 : table[count - 1]->next = NULL;
1083 [ + + ]: 135 : for (i = 1; i < count; sl = sl->next, i++)
1084 : 101 : sl->next = table[i];
1085 : 34 : free(table);
1086 : : }
1087 : :
1088 : : static struct fileset *
1089 : 297 : alternative_get_fileset(struct alternative *a, const char *file)
1090 : : {
1091 : : struct fileset *fs;
1092 : :
1093 [ + + ]: 422 : for (fs = a->choices; fs; fs = fs->next)
1094 [ + + ]: 274 : if (strcmp(fs->master_file, file) == 0)
1095 : 149 : return fs;
1096 : :
1097 : 148 : return NULL;
1098 : : }
1099 : :
1100 : : static struct slave_link *
1101 : 416 : alternative_get_slave(struct alternative *a, const char *name)
1102 : : {
1103 : : struct slave_link *sl;
1104 : :
1105 [ + + ]: 967 : for (sl = a->slaves; sl; sl = sl->next)
1106 [ + + ]: 582 : if (strcmp(sl->name, name) == 0)
1107 : 31 : return sl;
1108 : :
1109 : 385 : return NULL;
1110 : : }
1111 : :
1112 : : static bool
1113 : 92 : alternative_has_slave(struct alternative *a, const char *name)
1114 : : {
1115 : 92 : return alternative_get_slave(a, name) != NULL;
1116 : : }
1117 : :
1118 : : static bool
1119 : 57 : alternative_has_choice(struct alternative *a, const char *file)
1120 : : {
1121 : 57 : return alternative_get_fileset(a, file) != NULL;
1122 : : }
1123 : :
1124 : : static void
1125 : 174 : alternative_add_choice(struct alternative *a, struct fileset *fs)
1126 : : {
1127 : 174 : struct fileset *cur, *prev = NULL;
1128 : :
1129 : : /* Replace if already existing */
1130 [ + + ]: 250 : for (cur = a->choices; cur; cur = cur->next) {
1131 [ + + ]: 86 : if (strcmp(cur->master_file, fs->master_file) == 0) {
1132 : 10 : fs->next = cur->next;
1133 : 10 : fileset_free(cur);
1134 [ + + ]: 10 : if (prev)
1135 : 4 : prev->next = fs;
1136 : : else
1137 : 6 : a->choices = fs;
1138 : :
1139 : : /* XXX: Be smarter in detecting change? */
1140 : 10 : a->modified = true;
1141 : 10 : return;
1142 : : }
1143 : 76 : prev = cur;
1144 : : }
1145 : :
1146 : : /* Otherwise add at the end */
1147 [ + + ]: 164 : if (prev == NULL)
1148 : 103 : a->choices = fs;
1149 : : else
1150 : 61 : prev->next = fs;
1151 : 164 : fs->next = NULL;
1152 : 164 : a->modified = true;
1153 : : }
1154 : :
1155 : : static struct slave_link *
1156 : 416 : alternative_add_slave(struct alternative *a,
1157 : : const char *slave_name, const char *slave_link)
1158 : : {
1159 : : struct slave_link *sl, *new;
1160 : :
1161 : : /* Replace if already existing */
1162 [ + + ]: 706 : for (sl = a->slaves; sl; sl = sl->next) {
1163 [ + + ]: 582 : if (strcmp(sl->name, slave_name) == 0) {
1164 : 31 : free(sl->link);
1165 : 31 : sl->link = xstrdup(slave_link);
1166 : 31 : return sl;
1167 : : }
1168 [ + + ]: 551 : if (sl->next == NULL)
1169 : 261 : break;
1170 : : }
1171 : :
1172 : : /* Otherwise create new and add at the end */
1173 : 385 : new = xmalloc(sizeof(*new));
1174 : 385 : new->name = xstrdup(slave_name);
1175 : 385 : new->link = xstrdup(slave_link);
1176 : 385 : new->updated = false;
1177 : 385 : new->next = NULL;
1178 [ + + ]: 385 : if (sl)
1179 : 261 : sl->next = new;
1180 : : else
1181 : 124 : a->slaves = new;
1182 : :
1183 : 385 : return new;
1184 : : }
1185 : :
1186 : : static void
1187 : 40 : alternative_copy_slave(struct alternative *a, struct slave_link *sl)
1188 : : {
1189 : : struct slave_link *sl_new;
1190 : :
1191 : 40 : sl_new = alternative_add_slave(a, sl->name, sl->link);
1192 : 40 : sl_new->updated = sl->updated;
1193 : 40 : }
1194 : :
1195 : : static const char *
1196 : 82 : alternative_status_string(enum alternative_status status)
1197 : : {
1198 [ + + ]: 82 : return (status == ALT_ST_AUTO) ? "auto" : "manual";
1199 : : }
1200 : :
1201 : : static const char *
1202 : 9 : alternative_status_describe(enum alternative_status status)
1203 : : {
1204 [ + + ]: 9 : return (status == ALT_ST_AUTO) ? _("auto mode") : _("manual mode");
1205 : : }
1206 : :
1207 : : static void
1208 : 163 : alternative_set_status(struct alternative *a, enum alternative_status status)
1209 : : {
1210 [ + + + + ]: 163 : if (a->status == ALT_ST_UNKNOWN || status != a->status)
1211 : 162 : a->modified = true;
1212 : :
1213 [ + + + + ]: 163 : if (a->status != ALT_ST_UNKNOWN && status != a->status)
1214 : 14 : log_msg("status of link group %s set to %s", a->master_link,
1215 : : alternative_status_string(status));
1216 : :
1217 : 163 : a->status = status;
1218 : 163 : }
1219 : :
1220 : : static void
1221 : 151 : alternative_set_link(struct alternative *a, const char *linkname)
1222 : : {
1223 [ + + + + ]: 151 : if (a->master_link == NULL || strcmp(linkname, a->master_link) != 0)
1224 : 135 : a->modified = true;
1225 : :
1226 : 151 : free(a->master_link);
1227 : 151 : a->master_link = xstrdup(linkname);
1228 : 151 : }
1229 : :
1230 : : static bool
1231 : 7 : alternative_remove_choice(struct alternative *a, const char *file)
1232 : : {
1233 : : struct fileset *fs, *fs_prev;
1234 : :
1235 : 7 : fs_prev = NULL;
1236 [ + - ]: 10 : for (fs = a->choices; fs; fs = fs->next) {
1237 [ + + ]: 10 : if (strcmp(fs->master_file, file) != 0) {
1238 : 3 : fs_prev = fs;
1239 : 3 : continue;
1240 : : }
1241 [ + + ]: 7 : if (fs_prev)
1242 : 3 : fs_prev->next = fs->next;
1243 : : else
1244 : 4 : a->choices = fs->next;
1245 : 7 : fileset_free(fs);
1246 : 7 : a->modified = true;
1247 : 7 : return true;
1248 : : }
1249 : :
1250 : 0 : return false;
1251 : : }
1252 : :
1253 : : /*
1254 : : * Alternatives Database Load/Store functions.
1255 : : */
1256 : :
1257 : : enum LIBCOMPAT_ATTR_ENUM_FLAGS altdb_flags {
1258 : : ALTDB_LAX_PARSER = 1 << 0,
1259 : : ALTDB_WARN_PARSER = 1 << 1,
1260 : : };
1261 : :
1262 : : struct altdb_context {
1263 : : FILE *fh;
1264 : : char *filename;
1265 : : enum altdb_flags flags;
1266 : : bool modified;
1267 : : void LIBCOMPAT_ATTR_NORET
1268 : : (*bad_format)(struct altdb_context *, const char *msg);
1269 : : jmp_buf on_error;
1270 : : };
1271 : :
1272 : : static void
1273 : 21 : altdb_context_free(struct altdb_context *ctx)
1274 : : {
1275 [ + + ]: 21 : if (ctx->fh)
1276 : 3 : fclose(ctx->fh);
1277 : 21 : free(ctx->filename);
1278 : 21 : }
1279 : :
1280 : : static void LIBCOMPAT_ATTR_NORET LIBCOMPAT_ATTR_PRINTF(2)
1281 : 2 : altdb_bad_format(struct altdb_context *ctx, const char *format, ...)
1282 : : {
1283 : : va_list args;
1284 : : char *msg;
1285 : :
1286 : 2 : va_start(args, format);
1287 : 2 : msg = xvasprintf(format, args);
1288 : 2 : va_end(args);
1289 : :
1290 : 2 : ctx->bad_format(ctx, msg);
1291 : :
1292 : : /* This cannot happen, but just to make sure the bad_format() function
1293 : : * pointer is well implemented. */
1294 : : error("(internal) non returning bad-format function returned");
1295 : : }
1296 : :
1297 : : static int
1298 : 117 : altdb_filter_namelist(const struct dirent *entry)
1299 : : {
1300 [ + + ]: 117 : if (strcmp(entry->d_name, ".") == 0 ||
1301 [ + + ]: 74 : strcmp(entry->d_name, "..") == 0 ||
1302 [ + - ]: 31 : (strlen(entry->d_name) > strlen(ALT_TMP_EXT) &&
1303 [ - + ]: 31 : strcmp(entry->d_name + strlen(entry->d_name) -
1304 : : strlen(ALT_TMP_EXT), ALT_TMP_EXT) == 0))
1305 : 86 : return 0;
1306 : 31 : return 1;
1307 : : }
1308 : :
1309 : : static int
1310 : 58 : altdb_get_namelist(struct dirent ***table)
1311 : : {
1312 : : int count;
1313 : :
1314 : 58 : count = scandir(admdir, table, altdb_filter_namelist, alphasort);
1315 [ + + ]: 58 : if (count < 0) {
1316 [ - + ]: 15 : if (errno != ENOENT)
1317 : 0 : syserr(_("cannot scan directory '%.255s'"), admdir);
1318 : : /* The directory does not exist, proceed anyway. */
1319 : 15 : *table = NULL;
1320 : 15 : count = 0;
1321 : : }
1322 : :
1323 : 58 : return count;
1324 : : }
1325 : :
1326 : : static void
1327 : 58 : altdb_free_namelist(struct dirent **table, int n)
1328 : : {
1329 [ + + ]: 89 : while (n--)
1330 : 31 : free(table[n]);
1331 : 58 : free(table);
1332 : 58 : }
1333 : :
1334 : : static char *
1335 : 1691 : altdb_get_line(struct altdb_context *ctx, const char *name)
1336 : : {
1337 : : char *buf, *line;
1338 : : size_t len, bufsz, i;
1339 : :
1340 : 1691 : bufsz = 1024;
1341 : 1691 : buf = xmalloc(bufsz);
1342 : :
1343 : 1691 : for (i = 0; true; i += strlen(line)) {
1344 : 0 : errno = 0;
1345 : 1691 : line = fgets(buf + i, bufsz - i, ctx->fh);
1346 [ + - ]: 1691 : if (line) {
1347 [ - + - - ]: 1691 : if (strlen(buf) < bufsz - 1 || buf[bufsz - 2] == '\n')
1348 : : break;
1349 : : /* Need more space */
1350 : 0 : bufsz *= 2;
1351 : 0 : buf = realloc(buf, bufsz);
1352 [ # # ]: 0 : if (!buf)
1353 : 0 : error(_("failed to allocate memory"));
1354 : 0 : continue;
1355 : : }
1356 [ # # ]: 0 : if (feof(ctx->fh))
1357 : 0 : altdb_bad_format(ctx, _("unexpected end of file while trying "
1358 : : "to read %s"), name);
1359 : 0 : altdb_bad_format(ctx, _("while reading %s: %s"),
1360 : 0 : name, strerror(errno));
1361 : : }
1362 : :
1363 : 1691 : len = strlen(buf);
1364 [ + - - + ]: 1691 : if (len == 0 || buf[len - 1] != '\n') {
1365 : 0 : altdb_bad_format(ctx, _("line not terminated while trying "
1366 : : "to read %s"), name);
1367 : : }
1368 : 1691 : line[len - 1] = '\0';
1369 : :
1370 : 1691 : return buf;
1371 : : }
1372 : :
1373 : : static void LIBCOMPAT_ATTR_NORET
1374 : 1 : altdb_parse_error(struct altdb_context *ctx, const char *msg)
1375 : : {
1376 : 1 : error(_("%s corrupt: %s"), ctx->filename, msg);
1377 : : }
1378 : :
1379 : : static void LIBCOMPAT_ATTR_NORET
1380 : 1 : altdb_parse_stop(struct altdb_context *ctx, const char *msg)
1381 : : {
1382 : 1 : longjmp(ctx->on_error, 1);
1383 : : }
1384 : :
1385 : : static void
1386 : 873 : altdb_print_line(struct altdb_context *ctx, const char *line)
1387 : : {
1388 [ - + ]: 873 : if (strchr(line, '\n') != NULL)
1389 : 0 : error(_("newlines prohibited in update-alternatives files (%s)"),
1390 : : line);
1391 : :
1392 [ - + ]: 873 : if (fprintf(ctx->fh, "%s\n", line) < (int) strlen(line) + 1)
1393 : 0 : syserr(_("unable to write file '%s'"), ctx->filename);
1394 : 873 : }
1395 : :
1396 : : static bool
1397 : 372 : alternative_parse_slave(struct alternative *a, struct altdb_context *ctx)
1398 : : {
1399 : : char *name, *linkname;
1400 : : struct slave_link *sl;
1401 : :
1402 : 372 : name = altdb_get_line(ctx, _("slave name"));
1403 [ + + ]: 372 : if (!strlen(name)) { /* End of list */
1404 : 88 : free(name);
1405 : 88 : return false;
1406 : : }
1407 : 284 : sl = alternative_get_slave(a, name);
1408 [ - + ]: 284 : if (sl) {
1409 : 0 : free(name);
1410 : 0 : altdb_bad_format(ctx, _("duplicate slave name %s"), sl->name);
1411 : : }
1412 : :
1413 : 284 : linkname = altdb_get_line(ctx, _("slave link"));
1414 [ - + ]: 284 : if (strcmp(linkname, a->master_link) == 0) {
1415 : 0 : free(linkname);
1416 : 0 : free(name);
1417 : 0 : altdb_bad_format(ctx, _("slave link same as main link %s"),
1418 : : a->master_link);
1419 : : }
1420 [ + + ]: 674 : for (sl = a->slaves; sl; sl = sl->next) {
1421 [ - + ]: 390 : if (strcmp(linkname, sl->link) == 0) {
1422 : 0 : free(linkname);
1423 : 0 : free(name);
1424 : 0 : altdb_bad_format(ctx, _("duplicate slave link %s"),
1425 : : sl->link);
1426 : : }
1427 : : }
1428 : :
1429 : 284 : alternative_add_slave(a, name, linkname);
1430 : 284 : free(linkname);
1431 : 284 : free(name);
1432 : :
1433 : 284 : return true;
1434 : : }
1435 : :
1436 : : static bool
1437 : 229 : alternative_parse_fileset(struct alternative *a, struct altdb_context *ctx)
1438 : : {
1439 : : struct fileset *fs;
1440 : : struct slave_link *sl;
1441 : : char *master_file;
1442 : :
1443 : 229 : master_file = altdb_get_line(ctx, _("master file"));
1444 [ + + ]: 229 : if (!strlen(master_file)) { /* End of list */
1445 : 88 : free(master_file);
1446 : 88 : return false;
1447 : : }
1448 : :
1449 : 141 : fs = alternative_get_fileset(a, master_file);
1450 [ - + ]: 141 : if (fs)
1451 : 0 : altdb_bad_format(ctx, _("duplicate path %s"), master_file);
1452 : :
1453 [ - + ]: 141 : if (fsys_pathname_is_missing(master_file)) {
1454 : : char *junk;
1455 : :
1456 : : /* File not found - remove. */
1457 [ # # ]: 0 : if (ctx->flags & ALTDB_WARN_PARSER)
1458 : 0 : warning(_("alternative %s (part of link group %s) "
1459 : : "doesn't exist; removing from list of "
1460 : : "alternatives"), master_file, a->master_name);
1461 : 0 : junk = altdb_get_line(ctx, _("priority"));
1462 : 0 : free(junk);
1463 [ # # ]: 0 : for (sl = a->slaves; sl; sl = sl->next) {
1464 : 0 : junk = altdb_get_line(ctx, _("slave file"));
1465 : 0 : free(junk);
1466 : : }
1467 : 0 : ctx->modified = true;
1468 : : } else {
1469 : : char *prio_str, *prio_end;
1470 : : long prio;
1471 : :
1472 : 141 : prio_str = altdb_get_line(ctx, _("priority"));
1473 : 141 : errno = 0;
1474 : 141 : prio = strtol(prio_str, &prio_end, 10);
1475 : : /* XXX: Leak master_file/prio_str on non-fatal error */
1476 [ + - - + ]: 141 : if (prio_str == prio_end || *prio_end != '\0')
1477 : 0 : altdb_bad_format(ctx, _("priority of %s: %s"),
1478 : : master_file, prio_str);
1479 [ + - + - : 141 : if (prio < INT_MIN || prio > INT_MAX || errno == ERANGE)
- + ]
1480 : 0 : altdb_bad_format(ctx,
1481 : 0 : _("priority of %s is out of range: %s"),
1482 : : master_file, prio_str);
1483 : 141 : free(prio_str);
1484 : :
1485 : 141 : fs = fileset_new(master_file, prio);
1486 [ + + ]: 628 : for (sl = a->slaves; sl; sl = sl->next) {
1487 : 487 : char *slave_file = altdb_get_line(ctx, _("slave file"));
1488 : 487 : fileset_add_slave(fs, sl->name, slave_file);
1489 : 487 : free(slave_file);
1490 : : }
1491 : 141 : alternative_add_choice(a, fs);
1492 : : }
1493 : 141 : free(master_file);
1494 : :
1495 : 141 : return true;
1496 : : }
1497 : :
1498 : : static bool
1499 : 110 : alternative_load(struct alternative *a, enum altdb_flags flags)
1500 : : {
1501 : : struct altdb_context ctx;
1502 : : struct stat st;
1503 : : char *status;
1504 : : char *master_link;
1505 : :
1506 : : /* Initialize parse context */
1507 : 110 : ctx.modified = false;
1508 : 110 : ctx.flags = flags;
1509 [ + + ]: 110 : if (flags & ALTDB_LAX_PARSER)
1510 : 31 : ctx.bad_format = altdb_parse_stop;
1511 : : else
1512 : 79 : ctx.bad_format = altdb_parse_error;
1513 : 110 : ctx.filename = xasprintf("%s/%s", admdir, a->master_name);
1514 : :
1515 : : /* Open the alternative file. */
1516 : 110 : ctx.fh = fopen(ctx.filename, "r");
1517 [ + + ]: 110 : if (ctx.fh == NULL) {
1518 [ + - ]: 18 : if (errno == ENOENT) {
1519 : 18 : altdb_context_free(&ctx);
1520 : 18 : return false;
1521 : : }
1522 : :
1523 : 0 : syserr(_("unable to open file '%s'"), ctx.filename);
1524 : : }
1525 : :
1526 [ + + ]: 92 : if (setjmp(ctx.on_error)) {
1527 : 1 : altdb_context_free(&ctx);
1528 : 1 : alternative_reset(a);
1529 : 1 : return false;
1530 : : }
1531 : :
1532 : : /* Verify the alternative is not empty. */
1533 [ - + ]: 92 : if (fstat(fileno(ctx.fh), &st) < 0)
1534 : 0 : syserr(_("cannot stat file '%s'"), ctx.filename);
1535 [ + + ]: 92 : if (st.st_size == 0) {
1536 : 2 : altdb_context_free(&ctx);
1537 : 2 : alternative_reset(a);
1538 : 2 : return false;
1539 : : }
1540 : :
1541 : : /* Start parsing mandatory attributes (link+status) of the alternative */
1542 : 90 : alternative_reset(a);
1543 : 90 : status = altdb_get_line(&ctx, _("status"));
1544 [ + + + + ]: 90 : if (strcmp(status, "auto") != 0 && strcmp(status, "manual") != 0)
1545 : 2 : altdb_bad_format(&ctx, _("invalid status"));
1546 [ + + ]: 88 : alternative_set_status(a, (strcmp(status, "auto") == 0) ?
1547 : : ALT_ST_AUTO : ALT_ST_MANUAL);
1548 : 88 : free(status);
1549 : :
1550 : 88 : master_link = altdb_get_line(&ctx, _("master link"));
1551 : 88 : alternative_set_link(a, master_link);
1552 : 88 : free(master_link);
1553 : :
1554 : : /* Parse the description of the slaves links of the alternative */
1555 [ + + ]: 372 : while (alternative_parse_slave(a, &ctx));
1556 : :
1557 : : /* Parse the available choices in the alternative */
1558 [ + + ]: 229 : while (alternative_parse_fileset(a, &ctx)) ;
1559 : :
1560 : : /* Close database file */
1561 [ - + ]: 88 : if (fclose(ctx.fh))
1562 : 0 : syserr(_("unable to close file '%s'"), ctx.filename);
1563 : 88 : free(ctx.filename);
1564 : :
1565 : : /* Initialize the modified field which has been erroneously changed
1566 : : * by the various alternative_(add|set)_* calls:
1567 : : * false unless a choice has been auto-cleaned */
1568 : 88 : a->modified = ctx.modified;
1569 : :
1570 : 88 : return true;
1571 : : }
1572 : :
1573 : : static void
1574 : 45 : alternative_save(struct alternative *a)
1575 : : {
1576 : : struct altdb_context ctx;
1577 : : struct slave_link *sl, *sl_prev;
1578 : : struct fileset *fs;
1579 : : char *filenew, *file;
1580 : :
1581 : : /* Cleanup unused slaves before writing admin file. */
1582 : 45 : sl_prev = NULL;
1583 : 45 : sl = a->slaves;
1584 [ + + ]: 199 : while (sl) {
1585 : 154 : bool has_slave = false;
1586 : :
1587 [ + + ]: 231 : for (fs = a->choices; fs; fs = fs->next) {
1588 [ + + ]: 223 : if (fileset_has_slave(fs, sl->name)) {
1589 : 146 : has_slave = true;
1590 : 146 : break;
1591 : : }
1592 : : }
1593 : :
1594 [ + + ]: 154 : if (!has_slave) {
1595 : : struct slave_link *sl_rm;
1596 : :
1597 : 8 : verbose(_("discarding obsolete slave link %s (%s)"),
1598 : : sl->name, sl->link);
1599 [ + + ]: 8 : if (sl_prev)
1600 : 6 : sl_prev->next = sl->next;
1601 : : else
1602 : 2 : a->slaves = sl->next;
1603 : 8 : sl_rm = sl;
1604 : 8 : sl = sl->next;
1605 : 8 : slave_link_free(sl_rm);
1606 : : } else {
1607 : 146 : sl_prev = sl;
1608 : 146 : sl = sl->next;
1609 : : }
1610 : : }
1611 : :
1612 : : /* Sort entries */
1613 : 45 : alternative_sort_slaves(a);
1614 : 45 : alternative_sort_choices(a);
1615 : :
1616 : : /* Write admin file. */
1617 : 45 : file = xasprintf("%s/%s", admdir, a->master_name);
1618 : 45 : filenew = xasprintf("%s" ALT_TMP_EXT, file);
1619 : :
1620 : 45 : ctx.filename = filenew;
1621 : 45 : ctx.fh = fopen(ctx.filename, "w");
1622 [ - + - - ]: 45 : if (ctx.fh == NULL && errno == ENOENT) {
1623 [ # # ]: 0 : if (make_path(admdir, 0755) < 0)
1624 : 0 : syserr(_("cannot create administrative directory '%s'"),
1625 : : admdir);
1626 : 0 : ctx.fh = fopen(ctx.filename, "w");
1627 : : }
1628 [ - + ]: 45 : if (ctx.fh == NULL)
1629 : 0 : syserr(_("unable to create file '%s'"), ctx.filename);
1630 : :
1631 : 45 : altdb_print_line(&ctx, alternative_status_string(a->status));
1632 : 45 : altdb_print_line(&ctx, a->master_link);
1633 [ + + ]: 191 : for (sl = a->slaves; sl; sl = sl->next) {
1634 : 146 : altdb_print_line(&ctx, sl->name);
1635 : 146 : altdb_print_line(&ctx, sl->link);
1636 : : }
1637 : 45 : altdb_print_line(&ctx, "");
1638 : :
1639 [ + + ]: 118 : for (fs = a->choices; fs; fs = fs->next) {
1640 : : char *prio;
1641 : :
1642 : 73 : altdb_print_line(&ctx, fs->master_file);
1643 : :
1644 : 73 : prio = xasprintf("%d", fs->priority);
1645 : 73 : altdb_print_line(&ctx, prio);
1646 : 73 : free(prio);
1647 : :
1648 [ + + ]: 328 : for (sl = a->slaves; sl; sl = sl->next) {
1649 [ + + ]: 255 : if (fileset_has_slave(fs, sl->name))
1650 : 168 : altdb_print_line(&ctx,
1651 : 168 : fileset_get_slave(fs, sl->name));
1652 : : else
1653 : 87 : altdb_print_line(&ctx, "");
1654 : : }
1655 : : }
1656 : 45 : altdb_print_line(&ctx, "");
1657 : :
1658 : : /* Close database file */
1659 [ - + ]: 45 : if (fflush(ctx.fh))
1660 : 0 : syserr(_("unable to flush file '%s'"), ctx.filename);
1661 [ - + ]: 45 : if (fsync(fileno(ctx.fh)))
1662 : 0 : syserr(_("unable to sync file '%s'"), ctx.filename);
1663 [ - + ]: 45 : if (fclose(ctx.fh))
1664 : 0 : syserr(_("unable to close file '%s'"), ctx.filename);
1665 : :
1666 : : /* Put in place atomically. */
1667 : 45 : xrename(filenew, file);
1668 : :
1669 : 45 : free(filenew);
1670 : 45 : free(file);
1671 : 45 : }
1672 : :
1673 : : static const char *
1674 : 91 : alternative_set_current(struct alternative *a, char *new_choice)
1675 : : {
1676 : 91 : a->known_current = true;
1677 : 91 : a->current = new_choice;
1678 : :
1679 : 91 : return new_choice;
1680 : : }
1681 : :
1682 : : static const char *
1683 : 166 : alternative_get_current(struct alternative *a)
1684 : : {
1685 : : char *curlink;
1686 : : char *file;
1687 : :
1688 [ + + ]: 166 : if (a->known_current)
1689 : 75 : return a->current;
1690 : :
1691 : 91 : curlink = xasprintf("%s/%s", altdir, a->master_name);
1692 : 91 : file = fsys_areadlink(curlink);
1693 [ + + - + ]: 91 : if (file == NULL && errno != ENOENT)
1694 : 0 : syserr(_("cannot stat file '%s%s'"), instdir, curlink);
1695 : 91 : free(curlink);
1696 : :
1697 : 91 : return alternative_set_current(a, file);
1698 : : }
1699 : :
1700 : : static struct fileset *
1701 : 64 : alternative_get_best(struct alternative *a)
1702 : : {
1703 : : struct fileset *fs, *best;
1704 : : const char *current;
1705 : :
1706 : 64 : current = alternative_get_current(a);
1707 [ + + ]: 64 : if (current)
1708 : 48 : best = alternative_get_fileset(a, current);
1709 : : else
1710 : 16 : best = NULL;
1711 : :
1712 [ + + ]: 64 : if (best == NULL)
1713 : 23 : best = a->choices;
1714 : :
1715 [ + + ]: 163 : for (fs = a->choices; fs; fs = fs->next)
1716 [ + + ]: 99 : if (fs->priority > best->priority)
1717 : 11 : best = fs;
1718 : :
1719 : 64 : return best;
1720 : : }
1721 : :
1722 : : static void
1723 : 23 : alternative_display_query(struct alternative *a)
1724 : : {
1725 : : struct fileset *best, *fs;
1726 : : struct slave_link *sl;
1727 : : const char *current;
1728 : :
1729 : 23 : pr("Name: %s", a->master_name);
1730 : 23 : pr("Link: %s", a->master_link);
1731 [ + - ]: 23 : if (alternative_slaves_count(a) > 0) {
1732 : 23 : pr("Slaves:");
1733 [ + + ]: 97 : for (sl = a->slaves; sl; sl = sl->next)
1734 : 74 : pr(" %s %s", sl->name, sl->link);
1735 : : }
1736 : 23 : pr("Status: %s", alternative_status_string(a->status));
1737 : 23 : best = alternative_get_best(a);
1738 [ + - ]: 23 : if (best)
1739 : 23 : pr("Best: %s", best->master_file);
1740 : 23 : current = alternative_get_current(a);
1741 [ + - ]: 23 : pr("Value: %s", current ? current : "none");
1742 : :
1743 [ + + ]: 64 : for (fs = a->choices; fs; fs = fs->next) {
1744 : 41 : printf("\n");
1745 : 41 : pr("Alternative: %s", fs->master_file);
1746 : 41 : pr("Priority: %d", fs->priority);
1747 [ - + ]: 41 : if (alternative_slaves_count(a) == 0)
1748 : 0 : continue;
1749 : 41 : pr("Slaves:");
1750 [ + + ]: 184 : for (sl = a->slaves; sl; sl = sl->next) {
1751 [ + + ]: 143 : if (fileset_has_slave(fs, sl->name))
1752 : 87 : pr(" %s %s", sl->name,
1753 : 87 : fileset_get_slave(fs, sl->name));
1754 : : }
1755 : : }
1756 : 23 : }
1757 : :
1758 : : static void
1759 : 0 : alternative_display_user(struct alternative *a)
1760 : : {
1761 : : const char *current;
1762 : : struct fileset *fs;
1763 : : struct slave_link *sl;
1764 : :
1765 : 0 : pr("%s - %s", a->master_name, alternative_status_describe(a->status));
1766 : 0 : fs = alternative_get_best(a);
1767 [ # # ]: 0 : if (fs)
1768 : 0 : pr(_(" link best version is %s"), fs->master_file);
1769 : : else
1770 : 0 : pr(_(" link best version not available"));
1771 : 0 : current = alternative_get_current(a);
1772 [ # # ]: 0 : if (current) {
1773 : 0 : pr(_(" link currently points to %s"), current);
1774 : : } else {
1775 : 0 : pr(_(" link currently absent"));
1776 : : }
1777 : 0 : pr(_(" link %s is %s"), a->master_name, a->master_link);
1778 [ # # ]: 0 : for (sl = a->slaves; sl; sl = sl->next)
1779 : 0 : pr(_(" slave %s is %s"), sl->name, sl->link);
1780 : :
1781 [ # # ]: 0 : for (fs = a->choices; fs; fs = fs->next) {
1782 : 0 : pr(_("%s - priority %d"), fs->master_file, fs->priority);
1783 [ # # ]: 0 : for (sl = a->slaves; sl; sl = sl->next) {
1784 [ # # ]: 0 : if (fileset_has_slave(fs, sl->name))
1785 : 0 : pr(_(" slave %s: %s"), sl->name,
1786 : 0 : fileset_get_slave(fs, sl->name));
1787 : : }
1788 : : }
1789 : 0 : }
1790 : :
1791 : : static void
1792 : 0 : alternative_display_list(struct alternative *a)
1793 : : {
1794 : : struct fileset *fs;
1795 : :
1796 [ # # ]: 0 : for (fs = a->choices; fs; fs = fs->next)
1797 : 0 : pr("%s", fs->master_file);
1798 : 0 : }
1799 : :
1800 : : static void
1801 : 9 : alternative_print_choice(struct alternative *a, enum alternative_status status,
1802 : : struct fileset *fs, int idx, int len)
1803 : : {
1804 : 9 : const char *current = alternative_get_current(a);
1805 : : int mark;
1806 : :
1807 [ + + + - ]: 9 : if (a->status == status &&
1808 [ + + ]: 5 : current && strcmp(current, fs->master_file) == 0)
1809 : 3 : mark = '*';
1810 : : else
1811 : 6 : mark = ' ';
1812 : :
1813 : 9 : pr("%c %-12d %-*s % -10d %s", mark, idx, len,
1814 : : fs->master_file, fs->priority, alternative_status_describe(status));
1815 : 9 : }
1816 : :
1817 : : static char *
1818 : 3 : alternative_select_choice(struct alternative *a)
1819 : : {
1820 : : const char *current;
1821 : : char *ret, selection[_POSIX_PATH_MAX];
1822 : : struct fileset *best, *fs;
1823 : : int n_choices;
1824 : : int len;
1825 : :
1826 : 3 : n_choices = alternative_choices_count(a);
1827 : 3 : current = alternative_get_current(a);
1828 : 3 : best = alternative_get_best(a);
1829 [ - + ]: 3 : assert(best);
1830 : :
1831 : 3 : len = 15;
1832 [ + + ]: 9 : for (fs = a->choices; fs; fs = fs->next)
1833 [ + + ]: 6 : len = max(len, (int)strlen(fs->master_file) + 1);
1834 : :
1835 : 0 : for (;;) {
1836 : : int idx;
1837 : :
1838 : 3 : pr(P_("There is %d choice for the alternative %s (providing %s).",
1839 : : "There are %d choices for the alternative %s (providing %s).",
1840 : : n_choices), n_choices, a->master_name, a->master_link);
1841 : 3 : printf("\n");
1842 : :
1843 : 3 : pr(" %-12.12s %-*.*s %-10.10s %s", _("Selection"), len, len,
1844 : : _("Path"), _("Priority"), _("Status"));
1845 : 3 : pr("------------------------------------------------------------");
1846 : 3 : idx = 0;
1847 : 3 : alternative_print_choice(a, ALT_ST_AUTO, best, idx++, len);
1848 [ + + ]: 9 : for (fs = a->choices; fs; fs = fs->next, idx++)
1849 : 6 : alternative_print_choice(a, ALT_ST_MANUAL, fs, idx, len);
1850 : 3 : printf("\n");
1851 : 3 : printf(_("Press <enter> to keep the current choice[*], "
1852 : : "or type selection number: "));
1853 : 3 : ret = fgets(selection, sizeof(selection), stdin);
1854 [ + - - + ]: 3 : if (ret == NULL || strlen(selection) == 0) {
1855 : 0 : return NULL;
1856 : : }
1857 : 3 : selection[strlen(selection) - 1] = '\0';
1858 [ - + ]: 3 : if (strlen(selection) == 0)
1859 : 0 : return xstrdup(current);
1860 : 3 : errno = 0;
1861 : 3 : idx = strtol(selection, &ret, 10);
1862 [ + - + - : 3 : if (idx >= 0 && errno == 0 && *ret == '\0') {
+ + ]
1863 : : /* Look up by index */
1864 [ + - ]: 1 : if (idx == 0) {
1865 : 1 : alternative_set_status(a, ALT_ST_AUTO);
1866 : 1 : return xstrdup(best->master_file);
1867 : : }
1868 : 0 : idx--;
1869 [ # # # # ]: 0 : for (fs = a->choices; idx && fs; idx--)
1870 : 0 : fs = fs->next;
1871 [ # # ]: 0 : if (fs) {
1872 : 0 : alternative_set_status(a, ALT_ST_MANUAL);
1873 : 0 : return xstrdup(fs->master_file);
1874 : : }
1875 : : } else {
1876 : : /* Look up by name */
1877 : 2 : fs = alternative_get_fileset(a, selection);
1878 [ + - ]: 2 : if (fs) {
1879 : 2 : alternative_set_status(a, ALT_ST_MANUAL);
1880 : 2 : return xstrdup(selection);
1881 : : }
1882 : : }
1883 : : }
1884 : : }
1885 : :
1886 : : static char *
1887 : 3 : alternative_config(struct alternative *a, const char *current_choice)
1888 : : {
1889 : 3 : char *new_choice = NULL;
1890 : :
1891 [ - + ]: 3 : if (alternative_choices_count(a) == 0) {
1892 : 0 : pr(_("There is no program which provides %s."),
1893 : : a->master_name);
1894 : 0 : pr(_("Nothing to configure."));
1895 [ - + - - ]: 3 : } else if (opt_skip_auto && a->status == ALT_ST_AUTO) {
1896 : 0 : alternative_display_user(a);
1897 : : } else {
1898 : 3 : new_choice = alternative_select_choice(a);
1899 : : }
1900 : :
1901 : 3 : return new_choice;
1902 : : }
1903 : :
1904 : : static void
1905 : 395 : alternative_add_commit_op(struct alternative *a, enum opcode opcode,
1906 : : const char *arg_a, const char *arg_b)
1907 : : {
1908 : : struct commit_operation *op, *cur;
1909 : :
1910 : 395 : op = xmalloc(sizeof(*op));
1911 : 395 : op->opcode = opcode;
1912 : 395 : op->arg_a = xstrdup(arg_a);
1913 : 395 : op->arg_b = xstrdup(arg_b);
1914 : 395 : op->next = NULL;
1915 : :
1916 : : /* Add at the end */
1917 : 395 : cur = a->commit_ops;
1918 [ + + + + ]: 2515 : while (cur && cur->next)
1919 : 2120 : cur = cur->next;
1920 [ + + ]: 395 : if (cur)
1921 : 360 : cur->next = op;
1922 : : else
1923 : 35 : a->commit_ops = op;
1924 : 395 : }
1925 : :
1926 : : static void
1927 : 46 : alternative_commit(struct alternative *a)
1928 : : {
1929 : : struct commit_operation *op;
1930 : :
1931 [ + + ]: 433 : for (op = a->commit_ops; op; op = op->next) {
1932 [ - + + + : 387 : switch (op->opcode) {
- ]
1933 : 0 : case OPCODE_NOP:
1934 : 0 : break;
1935 : 60 : case OPCODE_RM:
1936 : 60 : fsys_rm(op->arg_a);
1937 : 60 : break;
1938 : 188 : case OPCODE_MV:
1939 : 188 : fsys_mv(op->arg_a, op->arg_b);
1940 : 188 : break;
1941 : 139 : case OPCODE_REF_TIME:
1942 : 139 : fsys_set_ref_time(op->arg_a, op->arg_b);
1943 : 139 : break;
1944 : : }
1945 : : }
1946 : :
1947 : 46 : alternative_commit_operations_free(a);
1948 : 46 : }
1949 : :
1950 : : enum alternative_path_status {
1951 : : ALT_PATH_SYMLINK,
1952 : : ALT_PATH_MISSING,
1953 : : ALT_PATH_OTHER,
1954 : : };
1955 : :
1956 : : static enum alternative_path_status
1957 : 198 : alternative_path_classify(const char *linkname)
1958 : : {
1959 : : struct stat st;
1960 : :
1961 [ + + ]: 198 : if (fsys_lstat(linkname, &st) < 0) {
1962 [ - + ]: 93 : if (errno != ENOENT)
1963 : 0 : syserr(_("cannot stat file '%s%s'"), instdir, linkname);
1964 : 93 : return ALT_PATH_MISSING;
1965 [ + + ]: 105 : } else if (S_ISLNK(st.st_mode)) {
1966 : 82 : return ALT_PATH_SYMLINK;
1967 : : } else {
1968 : 23 : return ALT_PATH_OTHER;
1969 : : }
1970 : : }
1971 : :
1972 : : static bool
1973 : 50 : alternative_path_can_remove(const char *linkname)
1974 : : {
1975 [ + + ]: 50 : if (opt_force)
1976 : 8 : return true;
1977 : :
1978 [ + + ]: 42 : if (alternative_path_classify(linkname) == ALT_PATH_OTHER)
1979 : 4 : return false;
1980 : : else
1981 : 38 : return true;
1982 : : }
1983 : :
1984 : : static bool
1985 : 121 : alternative_path_needs_update(const char *linkname, const char *filename)
1986 : : {
1987 : : char *linktarget;
1988 : : bool update;
1989 : :
1990 [ + + ]: 121 : if (opt_force)
1991 : 7 : return true;
1992 : :
1993 [ + + + ]: 114 : switch (alternative_path_classify(linkname)) {
1994 : 32 : case ALT_PATH_SYMLINK:
1995 : 32 : linktarget = fsys_xreadlink(linkname);
1996 [ + - ]: 32 : if (strcmp(linktarget, filename) == 0)
1997 : 32 : update = false;
1998 : : else
1999 : 0 : update = true;
2000 : 32 : free(linktarget);
2001 : :
2002 : 32 : return update;
2003 : 16 : case ALT_PATH_OTHER:
2004 : 16 : warning(_("not replacing %s with a link"), linkname);
2005 : 16 : return false;
2006 : 66 : case ALT_PATH_MISSING:
2007 : : default:
2008 : 66 : return true;
2009 : : }
2010 : : }
2011 : :
2012 : : static void
2013 : 121 : alternative_prepare_install_single(struct alternative *a, const char *name,
2014 : : const char *linkname, const char *file)
2015 : : {
2016 : : char *fntmp, *fn;
2017 : :
2018 : : /* Create alternatives directory (/etc/alternatives) if missing. */
2019 [ - + ]: 121 : if (fsys_pathname_is_missing(altdir)) {
2020 : 0 : char *root_altdir = fsys_get_path(altdir);
2021 : :
2022 [ # # ]: 0 : if (make_path(root_altdir, 0755) < 0)
2023 : 0 : syserr(_("cannot create alternatives directory '%s'"),
2024 : : root_altdir);
2025 : :
2026 : 0 : free(root_altdir);
2027 : : }
2028 : :
2029 : 121 : fn = xasprintf("%s/%s", altdir, name);
2030 : :
2031 : : /* Create link in /etc/alternatives. */
2032 : 121 : fntmp = xasprintf("%s/%s" ALT_TMP_EXT, altdir, name);
2033 : 121 : fsys_symlink(file, fntmp);
2034 : 121 : alternative_add_commit_op(a, OPCODE_MV, fntmp, fn);
2035 [ + + ]: 121 : if (fsys_pathname_is_missing(fn))
2036 : 76 : alternative_add_commit_op(a, OPCODE_REF_TIME, fn, file);
2037 : 121 : free(fntmp);
2038 : :
2039 [ + + ]: 121 : if (alternative_path_needs_update(linkname, fn)) {
2040 : : /* Create alternative link. */
2041 : 73 : fntmp = xasprintf("%s" ALT_TMP_EXT, linkname);
2042 : 73 : fsys_symlink(fn, fntmp);
2043 : 71 : alternative_add_commit_op(a, OPCODE_MV, fntmp, linkname);
2044 [ + + ]: 71 : if (fsys_pathname_is_missing(linkname))
2045 : 67 : alternative_add_commit_op(a, OPCODE_REF_TIME, linkname, fn);
2046 : 71 : free(fntmp);
2047 : : }
2048 : 119 : free(fn);
2049 : 119 : }
2050 : :
2051 : : static void
2052 : 35 : alternative_prepare_install(struct alternative *a, const char *choice)
2053 : : {
2054 : : struct slave_link *sl;
2055 : : struct fileset *fs;
2056 : :
2057 : 35 : fs = alternative_get_fileset(a, choice);
2058 [ - + ]: 35 : if (fs == NULL)
2059 : 0 : error(_("can't install unknown choice %s"), choice);
2060 : :
2061 : : /* Take care of master alternative */
2062 : 35 : alternative_prepare_install_single(a, a->master_name, a->master_link,
2063 : : choice);
2064 : :
2065 : : /* Take care of slaves alternatives */
2066 [ + + ]: 150 : for (sl = a->slaves; sl; sl = sl->next) {
2067 : : char *fn;
2068 : :
2069 [ + + ]: 117 : if (fileset_can_install_slave(fs, sl->name)) {
2070 : 172 : alternative_prepare_install_single(a, sl->name,
2071 : 86 : sl->link, fileset_get_slave(fs, sl->name));
2072 : 85 : continue;
2073 : : }
2074 : :
2075 : : /* Slave can't be installed */
2076 [ + + ]: 31 : if (fileset_has_slave(fs, sl->name))
2077 : 6 : warning(_("skip creation of %s because associated "
2078 : : "file %s (of link group %s) doesn't exist"),
2079 : 6 : sl->link, fileset_get_slave(fs, sl->name),
2080 : : a->master_name);
2081 : :
2082 : : /* Drop unused slave. */
2083 : 31 : fn = xasprintf("%s/%s", altdir, sl->name);
2084 [ + + ]: 31 : if (alternative_path_can_remove(sl->link))
2085 : 29 : alternative_add_commit_op(a, OPCODE_RM, sl->link, NULL);
2086 : : else
2087 : 2 : warning(_("not removing %s since it's not a symlink"),
2088 : : sl->link);
2089 : 31 : alternative_add_commit_op(a, OPCODE_RM, fn, NULL);
2090 : 31 : free(fn);
2091 : : }
2092 : 33 : }
2093 : :
2094 : : static void
2095 : 5 : alternative_remove_files(struct alternative *a)
2096 : : {
2097 : : struct slave_link *sl;
2098 : :
2099 : 5 : fsys_rm_args("%s" ALT_TMP_EXT, a->master_link);
2100 [ + + ]: 5 : if (alternative_path_can_remove(a->master_link))
2101 : 4 : fsys_rm(a->master_link);
2102 : :
2103 : 5 : fsys_rm_args("%s/%s" ALT_TMP_EXT, altdir, a->master_name);
2104 : 5 : fsys_rm_args("%s/%s", altdir, a->master_name);
2105 : :
2106 [ + + ]: 19 : for (sl = a->slaves; sl; sl = sl->next) {
2107 : 14 : fsys_rm_args("%s" ALT_TMP_EXT, sl->link);
2108 [ + + ]: 14 : if (alternative_path_can_remove(sl->link))
2109 : 13 : fsys_rm(sl->link);
2110 : :
2111 : 14 : fsys_rm_args("%s/%s" ALT_TMP_EXT, altdir, sl->name);
2112 : 14 : fsys_rm_args("%s/%s", altdir, sl->name);
2113 : : }
2114 : : /* Drop admin file */
2115 : 5 : xunlink_args("%s/%s", admdir, a->master_name);
2116 : 5 : }
2117 : :
2118 : : static char *
2119 : 7 : alternative_remove(struct alternative *a, const char *current_choice,
2120 : : const char *path)
2121 : : {
2122 : 7 : char *new_choice = NULL;
2123 : :
2124 [ + - ]: 7 : if (alternative_has_choice(a, path))
2125 : 7 : alternative_remove_choice(a, path);
2126 : : else
2127 : 0 : verbose(_("alternative %s for %s not registered; not removing"),
2128 : : path, a->master_name);
2129 : :
2130 [ + - + - ]: 7 : if (current_choice && strcmp(current_choice, path) == 0) {
2131 : : struct fileset *best;
2132 : :
2133 : : /* Current choice is removed. */
2134 [ + + ]: 7 : if (a->status == ALT_ST_MANUAL) {
2135 : : /* And it was manual, switch to auto. */
2136 : 3 : info(_("removing manually selected alternative "
2137 : : "- switching %s to auto mode"),
2138 : : a->master_name);
2139 : 3 : alternative_set_status(a, ALT_ST_AUTO);
2140 : : }
2141 : 7 : best = alternative_get_best(a);
2142 [ + + ]: 7 : if (best)
2143 : 4 : new_choice = xstrdup(best->master_file);
2144 : : }
2145 : :
2146 : 7 : return new_choice;
2147 : : }
2148 : :
2149 : : static bool
2150 : 90 : alternative_has_broken_symlink(const char *linkname, const char *ref_target)
2151 : : {
2152 : : char *target;
2153 : :
2154 : 90 : target = fsys_areadlink(linkname);
2155 [ + + ]: 90 : if (!target)
2156 : 4 : return true;
2157 [ - + ]: 86 : if (strcmp(target, ref_target) != 0) {
2158 : 0 : free(target);
2159 : 0 : return true;
2160 : : }
2161 : 86 : free(target);
2162 : 86 : return false;
2163 : : }
2164 : :
2165 : : static bool
2166 : 46 : alternative_has_broken_slave(struct slave_link *sl, struct fileset *fs)
2167 : : {
2168 [ + + ]: 46 : if (fileset_can_install_slave(fs, sl->name)) {
2169 : : char *wanted;
2170 : : const char *sl_target;
2171 : :
2172 : : /* Verify link -> /etc/alternatives/foo */
2173 : 36 : wanted = xasprintf("%s/%s", altdir, sl->name);
2174 [ - + ]: 36 : if (alternative_has_broken_symlink(sl->link, wanted)) {
2175 : 0 : free(wanted);
2176 : 0 : return true;
2177 : : }
2178 : :
2179 : : /* Verify /etc/alternatives/foo -> file */
2180 : 36 : sl_target = fileset_get_slave(fs, sl->name);
2181 [ - + ]: 36 : if (alternative_has_broken_symlink(wanted, sl_target)) {
2182 : 0 : free(wanted);
2183 : 0 : return true;
2184 : : }
2185 : :
2186 : 36 : free(wanted);
2187 : : } else {
2188 : : char *sl_altlnk;
2189 : :
2190 : : /* Slave link must not exist. */
2191 [ - + ]: 10 : if (alternative_path_classify(sl->link) != ALT_PATH_MISSING)
2192 : 0 : return true;
2193 : 10 : sl_altlnk = xasprintf("%s/%s", altdir, sl->name);
2194 [ + + ]: 10 : if (alternative_path_classify(sl_altlnk) != ALT_PATH_MISSING) {
2195 : 1 : free(sl_altlnk);
2196 : 1 : return true;
2197 : : }
2198 : 9 : free(sl_altlnk);
2199 : : }
2200 : :
2201 : 45 : return false;
2202 : : }
2203 : :
2204 : : static enum alternative_update_reason
2205 : 18 : alternative_needs_update(struct alternative *a)
2206 : : {
2207 : 18 : enum alternative_update_reason reason = ALT_UPDATE_NO;
2208 : : const char *current;
2209 : : char *wanted;
2210 : : struct fileset *fs;
2211 : : struct slave_link *sl;
2212 : :
2213 : : /* Check master link */
2214 : 18 : wanted = xasprintf("%s/%s", altdir, a->master_name);
2215 [ + + ]: 18 : if (alternative_has_broken_symlink(a->master_link, wanted)) {
2216 : 4 : free(wanted);
2217 : 4 : return ALT_UPDATE_LINK_BROKEN;
2218 : : }
2219 : 14 : free(wanted);
2220 : :
2221 : : /* Stop if we have an unmanaged alternative */
2222 : 14 : current = alternative_get_current(a);
2223 [ - + ]: 14 : if (current == NULL)
2224 : 0 : return ALT_UPDATE_LINK_BROKEN;
2225 : :
2226 : 14 : fs = alternative_get_fileset(a, current);
2227 : :
2228 : : /* Stop if we do not have the choice. */
2229 [ - + ]: 14 : if (fs == NULL)
2230 : 0 : return ALT_UPDATE_NO;
2231 : :
2232 : : /* Check slaves */
2233 [ + + ]: 60 : for (sl = a->slaves; sl; sl = sl->next) {
2234 [ + + ]: 46 : if (alternative_has_broken_slave(sl, fs)) {
2235 [ + - ]: 1 : if (sl->updated)
2236 : 1 : reason = ALT_UPDATE_SLAVE_CHANGED;
2237 : : else
2238 : 0 : return ALT_UPDATE_LINK_BROKEN;
2239 : : }
2240 : : }
2241 : :
2242 : 14 : return reason;
2243 : : }
2244 : :
2245 : : struct alternative_map {
2246 : : struct alternative_map *next;
2247 : :
2248 : : const char *key;
2249 : : struct alternative *item;
2250 : : };
2251 : :
2252 : : static struct alternative_map *
2253 : 277 : alternative_map_new(const char *key, struct alternative *a)
2254 : : {
2255 : : struct alternative_map *am;
2256 : :
2257 : 277 : am = xmalloc(sizeof(*am));
2258 : 277 : am->next = NULL;
2259 : 277 : am->key = key;
2260 : 277 : am->item = a;
2261 : :
2262 : 277 : return am;
2263 : : }
2264 : :
2265 : : static struct alternative *
2266 : 266 : alternative_map_find(struct alternative_map *am, const char *key)
2267 : : {
2268 [ + + ]: 669 : for (; am; am = am->next)
2269 [ + + + + ]: 505 : if (am->key && strcmp(am->key, key) == 0)
2270 : 102 : return am->item;
2271 : :
2272 : 164 : return NULL;
2273 : : }
2274 : :
2275 : : static void
2276 : 234 : alternative_map_add(struct alternative_map *am, const char *key,
2277 : : struct alternative *a)
2278 : : {
2279 : 234 : alternative_ref(a);
2280 : :
2281 [ + + ]: 234 : if (am->key == NULL) {
2282 : 56 : am->key = key;
2283 : 56 : am->item = a;
2284 : : } else {
2285 : 178 : struct alternative_map *new = alternative_map_new(key, a);
2286 : :
2287 [ + + ]: 424 : while (am->next)
2288 : 246 : am = am->next;
2289 : 178 : am->next = new;
2290 : : }
2291 : 234 : }
2292 : :
2293 : : static void
2294 : 17 : alternative_map_load_names(struct alternative_map *alt_map_obj)
2295 : : {
2296 : : struct dirent **table;
2297 : : int i, count;
2298 : :
2299 : 17 : count = altdb_get_namelist(&table);
2300 [ + + ]: 19 : for (i = 0; i < count; i++) {
2301 : 2 : struct alternative *a_new = alternative_new(table[i]->d_name);
2302 : :
2303 [ - + ]: 2 : if (!alternative_load(a_new, ALTDB_LAX_PARSER)) {
2304 : 0 : alternative_free(a_new);
2305 : 0 : continue;
2306 : : }
2307 : 2 : alternative_map_add(alt_map_obj, a_new->master_name, a_new);
2308 : :
2309 : 2 : alternative_unref(a_new);
2310 : : }
2311 : 17 : altdb_free_namelist(table, count);
2312 : 17 : }
2313 : :
2314 : : static void
2315 : 41 : alternative_map_load_tree(struct alternative_map *alt_map_links,
2316 : : struct alternative_map *alt_map_parent)
2317 : : {
2318 : : struct dirent **table;
2319 : : int i, count;
2320 : :
2321 : 41 : count = altdb_get_namelist(&table);
2322 [ + + ]: 70 : for (i = 0; i < count; i++) {
2323 : : struct slave_link *sl;
2324 : 29 : struct alternative *a_new = alternative_new(table[i]->d_name);
2325 : :
2326 [ + + ]: 29 : if (!alternative_load(a_new, ALTDB_LAX_PARSER)) {
2327 : 2 : alternative_free(a_new);
2328 : 2 : continue;
2329 : : }
2330 : 27 : alternative_map_add(alt_map_links, a_new->master_link, a_new);
2331 : 27 : alternative_map_add(alt_map_parent, a_new->master_name, a_new);
2332 [ + + ]: 116 : for (sl = a_new->slaves; sl; sl = sl->next) {
2333 : 89 : alternative_map_add(alt_map_links, sl->link, a_new);
2334 : 89 : alternative_map_add(alt_map_parent, sl->name, a_new);
2335 : : }
2336 : :
2337 : 27 : alternative_unref(a_new);
2338 : : }
2339 : 41 : altdb_free_namelist(table, count);
2340 : 41 : }
2341 : :
2342 : : static void
2343 : 85 : alternative_map_free(struct alternative_map *am)
2344 : : {
2345 : : struct alternative_map *am_next;
2346 : :
2347 [ + + ]: 292 : while (am) {
2348 : 207 : am_next = am->next;
2349 [ + + ]: 207 : if (am->item)
2350 : 164 : alternative_free(am->item);
2351 : 207 : free(am);
2352 : 207 : am = am_next;
2353 : : }
2354 : 85 : }
2355 : :
2356 : : static char *
2357 : 7 : alternative_set_manual(struct alternative *a, const char *path)
2358 : : {
2359 : 7 : char *new_choice = NULL;
2360 : :
2361 [ + - ]: 7 : if (alternative_has_choice(a, path))
2362 : 7 : new_choice = xstrdup(path);
2363 : : else
2364 : 0 : error(_("alternative %s for %s not registered; "
2365 : : "not setting"), path, a->master_name);
2366 : 7 : alternative_set_status(a, ALT_ST_MANUAL);
2367 : :
2368 : 7 : return new_choice;
2369 : : }
2370 : :
2371 : : static char *
2372 : 1 : alternative_set_auto(struct alternative *a)
2373 : : {
2374 : 1 : char *new_choice = NULL;
2375 : :
2376 : 1 : alternative_set_status(a, ALT_ST_AUTO);
2377 [ - + ]: 1 : if (alternative_choices_count(a) == 0)
2378 : 0 : info(_("there is no program which provides %s"),
2379 : : a->master_name);
2380 : : else
2381 : 1 : new_choice = xstrdup(alternative_get_best(a)->master_file);
2382 : :
2383 : 1 : return new_choice;
2384 : : }
2385 : :
2386 : : static const char *
2387 : 53 : get_argv_string(int argc, char **argv)
2388 : : {
2389 : : static char string[2048];
2390 : : size_t cur_len;
2391 : : int i;
2392 : :
2393 : 53 : string[0] = '\0';
2394 : 53 : cur_len = 0;
2395 [ + + ]: 979 : for (i = 1; i < argc; i++) {
2396 : 926 : size_t arg_len = strlen(argv[i]);
2397 : :
2398 [ - + ]: 926 : if (cur_len + arg_len + 2 > sizeof(string))
2399 : 0 : break;
2400 [ + + ]: 926 : if (cur_len) {
2401 : 873 : strcpy(string + cur_len, " ");
2402 : 873 : cur_len++;
2403 : : }
2404 : 926 : strcpy(string + cur_len, argv[i]);
2405 : 926 : cur_len += arg_len;
2406 : : }
2407 : :
2408 : 53 : return string;
2409 : : }
2410 : :
2411 : : static void
2412 : 53 : alternative_select_mode(struct alternative *a, const char *current_choice)
2413 : : {
2414 [ + + ]: 53 : if (current_choice) {
2415 : : /* Detect manually modified alternative, switch to manual. */
2416 [ - + ]: 37 : if (!alternative_has_choice(a, current_choice)) {
2417 [ # # ]: 0 : if (fsys_pathname_is_missing(current_choice)) {
2418 : 0 : warning(_("%s%s/%s is dangling; it will be updated "
2419 : : "with best choice"), instdir, altdir,
2420 : : a->master_name);
2421 : 0 : alternative_set_status(a, ALT_ST_AUTO);
2422 [ # # ]: 0 : } else if (a->status != ALT_ST_MANUAL) {
2423 : 0 : warning(_("%s%s/%s has been changed (manually or by "
2424 : : "a script); switching to manual "
2425 : : "updates only"), instdir, altdir,
2426 : : a->master_name);
2427 : 0 : alternative_set_status(a, ALT_ST_MANUAL);
2428 : : }
2429 : : }
2430 : : } else {
2431 : : /* Lack of alternative link => automatic mode. */
2432 : 16 : verbose(_("setting up automatic selection of %s"),
2433 : : a->master_name);
2434 : 16 : alternative_set_status(a, ALT_ST_AUTO);
2435 : : }
2436 : 53 : }
2437 : :
2438 : : static void
2439 : 40 : alternative_evolve_slave(struct alternative *a, const char *cur_choice,
2440 : : struct slave_link *sl, struct fileset *fs)
2441 : : {
2442 : : struct slave_link *sl_old;
2443 : 40 : char *new_file = NULL;
2444 : : const char *old, *new;
2445 : :
2446 : 40 : sl_old = alternative_get_slave(a, sl->name);
2447 [ + + ]: 40 : if (sl_old == NULL) {
2448 : 9 : sl->updated = true;
2449 : 9 : return;
2450 : : }
2451 : :
2452 : 31 : old = sl_old->link;
2453 : 31 : new = sl->link;
2454 : :
2455 [ + + + + ]: 31 : if (cur_choice && strcmp(cur_choice, fs->master_file) == 0) {
2456 : 19 : new_file = xstrdup(fileset_get_slave(fs, sl->name));
2457 : : } else {
2458 : : char *lnk;
2459 : :
2460 : 12 : lnk = xasprintf("%s/%s", altdir, sl->name);
2461 : 12 : new_file = fsys_areadlink(lnk);
2462 : 12 : free(lnk);
2463 : : }
2464 [ + + + - ]: 35 : if (strcmp(old, new) != 0 &&
2465 : 4 : alternative_path_classify(old) == ALT_PATH_SYMLINK) {
2466 : 4 : bool rename_link = false;
2467 : :
2468 [ + + ]: 4 : if (new_file)
2469 : 3 : rename_link = !fsys_pathname_is_missing(new_file);
2470 : :
2471 [ + + ]: 4 : if (rename_link) {
2472 : 2 : info(_("renaming %s slave link from %s%s to %s%s"),
2473 : : sl->name, instdir, old, instdir, new);
2474 : 2 : fsys_mv(old, new);
2475 : : } else {
2476 : 2 : fsys_rm(old);
2477 : : }
2478 : :
2479 : 4 : sl->updated = true;
2480 : : }
2481 : 31 : free(new_file);
2482 : : }
2483 : :
2484 : : static void
2485 : 18 : alternative_evolve(struct alternative *a, struct alternative *b,
2486 : : const char *cur_choice, struct fileset *fs)
2487 : : {
2488 : : struct slave_link *sl;
2489 : : bool is_link;
2490 : :
2491 : 18 : is_link = alternative_path_classify(a->master_link) == ALT_PATH_SYMLINK;
2492 [ + + + + ]: 18 : if (is_link && strcmp(a->master_link, b->master_link) != 0) {
2493 : 2 : info(_("renaming %s link from %s%s to %s%s"), b->master_name,
2494 : : instdir, a->master_link, instdir, b->master_link);
2495 : 2 : fsys_mv(a->master_link, b->master_link);
2496 : : }
2497 : 18 : alternative_set_link(a, b->master_link);
2498 : :
2499 : : /* Check if new slaves have been added, or existing
2500 : : * ones renamed. */
2501 [ + + ]: 58 : for (sl = b->slaves; sl; sl = sl->next) {
2502 : 40 : alternative_evolve_slave(a, cur_choice, sl, fs);
2503 : 40 : alternative_copy_slave(a, sl);
2504 : : }
2505 : 18 : }
2506 : :
2507 : : static char *
2508 : 33 : alternative_install(struct alternative **aptr, struct alternative *inst_alt,
2509 : : const char *current_choice, struct fileset *fileset)
2510 : : {
2511 : 33 : struct alternative *a = *aptr;
2512 : 33 : char *new_choice = NULL;
2513 : :
2514 [ + + ]: 33 : if (a->master_link) {
2515 : : /* Alternative already exists, check if anything got
2516 : : * updated. */
2517 : 18 : alternative_evolve(a, inst_alt, current_choice, fileset);
2518 : 18 : alternative_free(inst_alt);
2519 : : } else {
2520 : : /* Alternative doesn't exist, create from parameters. */
2521 : 15 : alternative_free(a);
2522 : 15 : *aptr = a = inst_alt;
2523 : : }
2524 : 33 : alternative_add_choice(a, fileset);
2525 [ + + ]: 33 : if (a->status == ALT_ST_AUTO) {
2526 : 30 : new_choice = xstrdup(alternative_get_best(a)->master_file);
2527 : : } else {
2528 : 3 : verbose(_("automatic updates of %s/%s are disabled; "
2529 : : "leaving it alone"), altdir, a->master_name);
2530 : 3 : verbose(_("to return to automatic updates use "
2531 : : "'%s --auto %s'"), PROGNAME, a->master_name);
2532 : : }
2533 : 33 : return new_choice;
2534 : : }
2535 : :
2536 : : static void
2537 : 53 : alternative_update(struct alternative *a,
2538 : : const char *current_choice, const char *new_choice)
2539 : : {
2540 : : enum alternative_update_reason reason;
2541 : :
2542 : : /* No choice left, remove everything. */
2543 [ + + ]: 53 : if (!alternative_choices_count(a)) {
2544 : 5 : log_msg("link group %s fully removed", a->master_name);
2545 : 5 : alternative_remove_files(a);
2546 : 5 : return;
2547 : : }
2548 : :
2549 : : /* New choice wanted. */
2550 [ + + + + ]: 48 : if (new_choice &&
2551 [ + + ]: 29 : (!current_choice || strcmp(new_choice, current_choice) != 0)) {
2552 : 30 : log_msg("link group %s updated to point to %s", a->master_name,
2553 : : new_choice);
2554 [ + + ]: 30 : if (a->status == ALT_ST_AUTO)
2555 : 24 : info(_("using %s to provide %s (%s) in auto mode"),
2556 : : new_choice, a->master_link, a->master_name);
2557 : : else
2558 : 6 : info(_("using %s to provide %s (%s) in manual mode"),
2559 : : new_choice, a->master_link, a->master_name);
2560 : 30 : debug("prepare_install(%s)", new_choice);
2561 : 30 : alternative_prepare_install(a, new_choice);
2562 [ + + ]: 18 : } else if ((reason = alternative_needs_update(a))) {
2563 [ + + ]: 5 : if (reason == ALT_UPDATE_SLAVE_CHANGED) {
2564 : 1 : log_msg("link group %s updated with changed slaves",
2565 : : a->master_name);
2566 : 1 : info(_("updating alternative %s "
2567 : : "because link group %s has changed slave links"),
2568 : : current_choice, a->master_name);
2569 : : } else {
2570 : 4 : log_msg("auto-repair link group %s", a->master_name);
2571 : 4 : warning(_("forcing reinstallation of alternative %s "
2572 : : "because link group %s is broken"),
2573 : : current_choice, a->master_name);
2574 : : }
2575 : :
2576 [ + - - + ]: 5 : if (current_choice && !alternative_has_choice(a, current_choice)) {
2577 : 0 : struct fileset *best = alternative_get_best(a);
2578 : :
2579 : 0 : warning(_("current alternative %s is unknown, "
2580 : : "switching to %s for link group %s"),
2581 : : current_choice, best->master_file,
2582 : : a->master_name);
2583 : 0 : current_choice = best->master_file;
2584 : 0 : alternative_set_status(a, ALT_ST_AUTO);
2585 : : }
2586 : :
2587 [ + - ]: 5 : if (current_choice)
2588 : 5 : alternative_prepare_install(a, current_choice);
2589 : : }
2590 : :
2591 : : /* Save administrative file if needed. */
2592 [ + + ]: 46 : if (a->modified) {
2593 : 45 : debug("%s is modified and will be saved", a->master_name);
2594 : 45 : alternative_save(a);
2595 : : }
2596 : :
2597 : : /* Replace all symlinks in one pass. */
2598 : 46 : alternative_commit(a);
2599 : : }
2600 : :
2601 : : static void
2602 : 0 : alternative_config_all(void)
2603 : : {
2604 : : struct alternative_map *alt_map_obj;
2605 : : struct alternative_map *am;
2606 : :
2607 : 0 : alt_map_obj = alternative_map_new(NULL, NULL);
2608 : 0 : alternative_map_load_names(alt_map_obj);
2609 : :
2610 [ # # # # ]: 0 : for (am = alt_map_obj; am && am->item; am = am->next) {
2611 : : const char *current_choice;
2612 : : char *new_choice;
2613 : :
2614 : 0 : current_choice = alternative_get_current(am->item);
2615 : 0 : alternative_select_mode(am->item, current_choice);
2616 : :
2617 : 0 : new_choice = alternative_config(am->item, current_choice);
2618 : :
2619 : 0 : alternative_update(am->item, current_choice, new_choice);
2620 : :
2621 : 0 : free(new_choice);
2622 : : }
2623 : :
2624 : 0 : alternative_map_free(alt_map_obj);
2625 : 0 : }
2626 : :
2627 : : static void
2628 : 15 : alternative_get_selections(void)
2629 : : {
2630 : : struct alternative_map *alt_map_obj;
2631 : : struct alternative_map *am;
2632 : :
2633 : 15 : alt_map_obj = alternative_map_new(NULL, NULL);
2634 : 15 : alternative_map_load_names(alt_map_obj);
2635 : :
2636 [ + - - + ]: 15 : for (am = alt_map_obj; am && am->item; am = am->next) {
2637 : : const char *current;
2638 : :
2639 : 0 : current = alternative_get_current(am->item);
2640 [ # # ]: 0 : printf("%-30s %-8s %s\n", am->key,
2641 : 0 : alternative_status_string(am->item->status),
2642 : : current ? current : "");
2643 : : }
2644 : :
2645 : 15 : alternative_map_free(alt_map_obj);
2646 : 15 : }
2647 : :
2648 : : static void
2649 : 3 : alternative_set_selection(struct alternative_map *all, const char *name,
2650 : : const char *status, const char *choice)
2651 : : {
2652 : : struct alternative *a;
2653 : :
2654 : 3 : debug("set_selection(%s, %s, %s)", name, status, choice);
2655 : 3 : a = alternative_map_find(all, name);
2656 [ + + ]: 3 : if (a) {
2657 : 2 : char *new_choice = NULL;
2658 : :
2659 [ + + ]: 2 : if (strcmp(status, "auto") == 0) {
2660 : 1 : info(_("selecting alternative %s as auto"), name);
2661 : 1 : new_choice = alternative_set_auto(a);
2662 [ + - ]: 1 : } else if (alternative_has_choice(a, choice)) {
2663 : 1 : info(_("selecting alternative %s as choice %s"), name,
2664 : : choice);
2665 : 1 : new_choice = alternative_set_manual(a, choice);
2666 : : } else {
2667 : 0 : info(_("alternative %s unchanged because choice "
2668 : : "%s is not available"), name, choice);
2669 : : }
2670 : :
2671 [ + - ]: 2 : if (new_choice) {
2672 : : const char *current_choice;
2673 : :
2674 : 2 : current_choice = alternative_get_current(a);
2675 : 2 : alternative_select_mode(a, current_choice);
2676 : :
2677 : 2 : alternative_update(a, current_choice, new_choice);
2678 : :
2679 : 2 : free(new_choice);
2680 : : }
2681 : : } else {
2682 : 1 : info(_("skip unknown alternative %s"), name);
2683 : : }
2684 : 3 : }
2685 : :
2686 : : static void
2687 : 2 : alternative_set_selections(FILE *input, const char *desc)
2688 : : {
2689 : : struct alternative_map *alt_map_obj;
2690 : :
2691 : 2 : alt_map_obj = alternative_map_new(NULL, NULL);
2692 : 2 : alternative_map_load_names(alt_map_obj);
2693 : :
2694 : 3 : for (;;) {
2695 : : char line[1024], *res, *name, *status, *choice;
2696 : : size_t len, i;
2697 : :
2698 : 5 : errno = 0;
2699 : : /* Can't use scanf("%s %s %s") because choice can
2700 : : * contain a space */
2701 : 5 : res = fgets(line, sizeof(line), input);
2702 [ + + - + ]: 5 : if (res == NULL && errno) {
2703 : 0 : syserr(_("read error in %.250s"), desc);
2704 [ + + ]: 5 : } else if (res == NULL) {
2705 : 2 : break;
2706 : : }
2707 : 3 : len = strlen(line);
2708 [ + - - + ]: 3 : if (len == 0 || line[len - 1] != '\n') {
2709 : 0 : error(_("line too long or not terminated while "
2710 : : "trying to read %s"), desc);
2711 : : }
2712 : 3 : line[len - 1] = '\0';
2713 : 3 : len--;
2714 : :
2715 : : /* Delimit name string in line */
2716 : 3 : i = 0;
2717 : 3 : name = line;
2718 [ + - + + ]: 38 : while (i < len && !isblank(line[i]))
2719 : 35 : i++;
2720 [ - + ]: 3 : if (i >= len) {
2721 : 0 : info(_("skip invalid selection line: %s"), line);
2722 : 0 : continue;
2723 : : }
2724 : 3 : line[i++] = '\0';
2725 [ + - - + ]: 3 : while (i < len && isblank(line[i]))
2726 : 0 : i++;
2727 : :
2728 : : /* Delimit status string in line */
2729 : 3 : status = line + i;
2730 [ + - + + ]: 17 : while (i < len && !isblank(line[i]))
2731 : 14 : i++;
2732 [ - + ]: 3 : if (i >= len) {
2733 : 0 : info(_("skip invalid selection line: %s"), line);
2734 : 0 : continue;
2735 : : }
2736 : 3 : line[i++] = '\0';
2737 [ + - - + ]: 3 : while (i < len && isblank(line[i]))
2738 : 0 : i++;
2739 : :
2740 : : /* Delimit choice string in the line */
2741 [ - + ]: 3 : if (i >= len) {
2742 : 0 : info(_("skip invalid selection line: %s"), line);
2743 : 0 : continue;
2744 : : }
2745 : 3 : choice = line + i;
2746 : :
2747 : 3 : alternative_set_selection(alt_map_obj, name, status, choice);
2748 : : }
2749 : :
2750 : 2 : alternative_map_free(alt_map_obj);
2751 : 2 : }
2752 : :
2753 : : static void
2754 : 181 : alternative_check_name(const char *name)
2755 : : {
2756 [ + + ]: 181 : if (strpbrk(name, "/ \t"))
2757 : 2 : error(_("alternative name (%s) must not contain '/' "
2758 : : "and spaces"), name);
2759 : 179 : }
2760 : :
2761 : : static void
2762 : 134 : alternative_check_link(const char *linkname)
2763 : : {
2764 [ + + ]: 134 : if (linkname[0] != '/')
2765 : 1 : error(_("alternative link is not absolute as it should be: %s"),
2766 : : linkname);
2767 : 133 : }
2768 : :
2769 : : static void
2770 : 147 : alternative_check_path(const char *file)
2771 : : {
2772 [ + - + + ]: 147 : if (!file || file[0] != '/')
2773 : 1 : error(_("alternative path is not absolute as it should be: %s"),
2774 : : file);
2775 : 146 : }
2776 : :
2777 : : /**
2778 : : * Check the alternative installation arguments.
2779 : : *
2780 : : * That the caller doesn't mix links between alternatives, doesn't mix
2781 : : * alternatives between slave/master, and that the various parameters
2782 : : * are fine.
2783 : : */
2784 : : static void
2785 : 44 : alternative_check_install_args(struct alternative *inst_alt,
2786 : : struct fileset *fileset)
2787 : : {
2788 : : struct alternative_map *alt_map_links, *alt_map_parent;
2789 : : struct alternative *found;
2790 : : struct slave_link *sl;
2791 : :
2792 : 44 : alternative_check_name(inst_alt->master_name);
2793 : 43 : alternative_check_link(inst_alt->master_link);
2794 : 42 : alternative_check_path(fileset->master_file);
2795 : :
2796 : : /* Load information about all alternatives to check for mistakes. */
2797 : 41 : alt_map_links = alternative_map_new(NULL, NULL);
2798 : 41 : alt_map_parent = alternative_map_new(NULL, NULL);
2799 : 41 : alternative_map_load_tree(alt_map_links, alt_map_parent);
2800 : :
2801 : 41 : found = alternative_map_find(alt_map_parent, inst_alt->master_name);
2802 [ + + + + ]: 41 : if (found && strcmp(found->master_name, inst_alt->master_name) != 0) {
2803 : 1 : error(_("alternative %s can't be master: it is a slave of %s"),
2804 : : inst_alt->master_name, found->master_name);
2805 : : }
2806 : :
2807 : 40 : found = alternative_map_find(alt_map_links, inst_alt->master_link);
2808 [ + + + + ]: 40 : if (found && strcmp(found->master_name, inst_alt->master_name) != 0) {
2809 : 1 : found = alternative_map_find(alt_map_parent,
2810 : 1 : found->master_name);
2811 : 1 : error(_("alternative link %s is already managed by %s"),
2812 : : inst_alt->master_link, found->master_name);
2813 : : }
2814 : :
2815 [ + + ]: 39 : if (fsys_pathname_is_missing(fileset->master_file))
2816 : 1 : error(_("alternative path %s%s doesn't exist"),
2817 : : instdir, fileset->master_file);
2818 : :
2819 [ + + ]: 126 : for (sl = inst_alt->slaves; sl; sl = sl->next) {
2820 : 92 : const char *file = fileset_get_slave(fileset, sl->name);
2821 : :
2822 : 92 : alternative_check_name(sl->name);
2823 : 91 : alternative_check_link(sl->link);
2824 : 91 : alternative_check_path(file);
2825 : :
2826 : 91 : found = alternative_map_find(alt_map_parent, sl->name);
2827 [ + + ]: 91 : if (found &&
2828 [ + + ]: 32 : strcmp(found->master_name, inst_alt->master_name) != 0) {
2829 [ + - ]: 1 : if (strcmp(found->master_name, sl->name) == 0)
2830 : 1 : error(_("alternative %s can't be slave of %s: "
2831 : : "it is a master alternative"),
2832 : : sl->name, inst_alt->master_name);
2833 : : else
2834 : 0 : error(_("alternative %s can't be slave of %s: "
2835 : : "it is a slave of %s"),
2836 : : sl->name, inst_alt->master_name,
2837 : : found->master_name);
2838 : : }
2839 : :
2840 : 90 : found = alternative_map_find(alt_map_links, sl->link);
2841 [ + + ]: 90 : if (found &&
2842 [ + + ]: 29 : strcmp(found->master_name, inst_alt->master_name) != 0) {
2843 : 1 : error(_("alternative link %s is already "
2844 : : "managed by %s"), sl->link,
2845 : : found->master_name);
2846 : : }
2847 [ + + ]: 89 : if (found) {
2848 : : struct slave_link *sl2;
2849 : :
2850 [ + - ]: 56 : for (sl2 = found->slaves; sl2; sl2 = sl2->next)
2851 [ + + ]: 56 : if (strcmp(sl2->link, sl->link) == 0)
2852 : 28 : break;
2853 [ + - + + ]: 28 : if (sl2 && strcmp(sl2->name, sl->name) != 0)
2854 : 1 : error(_("alternative link %s is already "
2855 : : "managed by %s (slave of %s)"),
2856 : : sl->link, sl2->name,
2857 : : found->master_name);
2858 : : }
2859 : : }
2860 : :
2861 : 34 : alternative_map_free(alt_map_links);
2862 : 34 : alternative_map_free(alt_map_parent);
2863 : 34 : }
2864 : :
2865 : : /*
2866 : : * Main program
2867 : : */
2868 : :
2869 : : static void
2870 : 107 : set_action(enum action new_action)
2871 : : {
2872 [ - + ]: 107 : if (action)
2873 : 0 : badusage(_("two commands specified: --%s and --%s"),
2874 : : action_names[action].name, action_names[new_action].name);
2875 : 107 : action = new_action;
2876 : 107 : }
2877 : :
2878 : : static void
2879 : 62 : set_action_from_name(const char *new_action)
2880 : : {
2881 : : size_t i;
2882 : :
2883 [ + - ]: 526 : for (i = 0; i < array_count(action_names); i++) {
2884 [ + + ]: 526 : if (strcmp(new_action, action_names[i].name) == 0) {
2885 : 62 : set_action(action_names[i].action);
2886 : 62 : return;
2887 : : }
2888 : : }
2889 : :
2890 : 0 : assert(!"unknown action name");
2891 : : }
2892 : :
2893 : : static const char *
2894 : 9 : set_rootdir(const char *dir)
2895 : : {
2896 : 9 : instdir = fsys_set_dir(dir);
2897 : 9 : free(log_file);
2898 : 9 : log_file = fsys_get_path(LOGDIR "/alternatives.log");
2899 : 9 : altdir = SYSCONFDIR "/alternatives";
2900 : 9 : free(admdir);
2901 : 9 : admdir = fsys_gen_admindir();
2902 : :
2903 : 9 : return instdir;
2904 : : }
2905 : :
2906 : : static char *
2907 : 107 : admindir_init(void)
2908 : : {
2909 : : const char *basedir_env;
2910 : :
2911 : : /* Try to get the admindir from an environment variable, usually set
2912 : : * by the system package manager. */
2913 : 107 : basedir_env = getenv(ADMINDIR_ENVVAR);
2914 [ + + ]: 107 : if (basedir_env)
2915 : 10 : return xasprintf("%s%s", basedir_env, "/alternatives");
2916 : : else
2917 : 97 : return fsys_gen_admindir();
2918 : : }
2919 : :
2920 : : #define MISSING_ARGS(nb) (argc < i + nb + 1)
2921 : :
2922 : : int
2923 : 107 : main(int argc, char **argv)
2924 : : {
2925 : : /* Alternative worked on. */
2926 : 107 : struct alternative *a = NULL;
2927 : : /* Alternative to install. */
2928 : 107 : struct alternative *inst_alt = NULL;
2929 : : /* Set of files to install in the alternative. */
2930 : 107 : struct fileset *fileset = NULL;
2931 : : /* Path of alternative we are offering. */
2932 : 107 : const char *path = NULL;
2933 : 107 : const char *current_choice = NULL;
2934 : 107 : char *new_choice = NULL;
2935 : 107 : bool modifies_alt = false;
2936 : 107 : bool modifies_sys = false;
2937 : 107 : int i = 0;
2938 : :
2939 : 107 : setlocale(LC_ALL, "");
2940 : 107 : bindtextdomain(PACKAGE, LOCALEDIR);
2941 : 107 : textdomain(PACKAGE);
2942 : :
2943 : 107 : tzset();
2944 : 107 : umask(022);
2945 : :
2946 : 107 : instdir = fsys_set_dir(NULL);
2947 : 107 : admdir = admindir_init();
2948 : 107 : log_file = fsys_get_path(LOGDIR "/alternatives.log");
2949 : :
2950 [ - + ]: 107 : if (setvbuf(stdout, NULL, _IONBF, 0))
2951 : 0 : syserr("setvbuf failed");
2952 : :
2953 : 107 : prog_path = argv[0];
2954 : :
2955 [ + + ]: 725 : for (i = 1; i < argc; i++) {
2956 [ - + ]: 619 : if (strstr(argv[i], "--") != argv[i]) {
2957 : 0 : error(_("unknown argument '%s'"), argv[i]);
2958 [ - + ]: 619 : } else if (strcmp("--help", argv[i]) == 0) {
2959 : 0 : usage();
2960 : 0 : exit(0);
2961 [ - + ]: 619 : } else if (strcmp("--version", argv[i]) == 0) {
2962 : 0 : version();
2963 : 0 : exit(0);
2964 [ + + ]: 619 : } else if (strcmp("--quiet", argv[i]) == 0) {
2965 : 92 : opt_verbose = OUTPUT_QUIET;
2966 [ - + ]: 527 : } else if (strcmp("--verbose", argv[i]) == 0) {
2967 : 0 : opt_verbose = OUTPUT_VERBOSE;
2968 [ + + ]: 527 : } else if (strcmp("--debug", argv[i]) == 0) {
2969 : 15 : opt_verbose = OUTPUT_DEBUG;
2970 [ + + ]: 512 : } else if (strcmp("--install", argv[i]) == 0) {
2971 : : const char *alink, *aname, *apath;
2972 : : char *prio_str, *prio_end;
2973 : : long prio;
2974 : :
2975 : 45 : set_action(ACTION_INSTALL);
2976 [ - + ]: 45 : if (MISSING_ARGS(4))
2977 : 0 : badusage(_("--%s needs <link> <name> <path> "
2978 : 0 : "<priority>"), argv[i] + 2);
2979 : :
2980 : 45 : alink = argv[i + 1];
2981 : 45 : aname = argv[i + 2];
2982 : 45 : apath = argv[i + 3];
2983 : 45 : prio_str = argv[i + 4];
2984 : :
2985 [ - + ]: 45 : if (strcmp(alink, apath) == 0)
2986 : 0 : badusage(_("<link> '%s' is the same as <path>"),
2987 : : alink);
2988 : 45 : errno = 0;
2989 : 45 : prio = strtol(prio_str, &prio_end, 10);
2990 [ + - - + ]: 45 : if (prio_str == prio_end || *prio_end != '\0')
2991 : 0 : badusage(_("priority '%s' must be an integer"),
2992 : : prio_str);
2993 [ + - + - : 45 : if (prio < INT_MIN || prio > INT_MAX || errno == ERANGE)
- + ]
2994 : 0 : badusage(_("priority '%s' is out of range"),
2995 : : prio_str);
2996 : :
2997 : 45 : a = alternative_new(aname);
2998 : 45 : inst_alt = alternative_new(aname);
2999 : 45 : alternative_set_status(inst_alt, ALT_ST_AUTO);
3000 : 45 : alternative_set_link(inst_alt, alink);
3001 : 45 : fileset = fileset_new(apath, prio);
3002 : :
3003 : 45 : i += 4;
3004 [ + + ]: 467 : } else if (strcmp("--remove", argv[i]) == 0 ||
3005 [ + + ]: 459 : strcmp("--set", argv[i]) == 0) {
3006 : 14 : set_action_from_name(argv[i] + 2);
3007 [ - + ]: 14 : if (MISSING_ARGS(2))
3008 : 0 : badusage(_("--%s needs <name> <path>"), argv[i] + 2);
3009 : :
3010 : 14 : a = alternative_new(argv[i + 1]);
3011 : 14 : path = argv[i + 2];
3012 : :
3013 : 14 : alternative_check_name(a->master_name);
3014 : 14 : alternative_check_path(path);
3015 : :
3016 : 14 : i += 2;
3017 [ + - ]: 453 : } else if (strcmp("--display", argv[i]) == 0 ||
3018 [ + + ]: 453 : strcmp("--query", argv[i]) == 0 ||
3019 [ + - ]: 427 : strcmp("--auto", argv[i]) == 0 ||
3020 [ + + ]: 427 : strcmp("--config", argv[i]) == 0 ||
3021 [ + - ]: 424 : strcmp("--list", argv[i]) == 0 ||
3022 [ + + ]: 424 : strcmp("--remove-all", argv[i]) == 0) {
3023 : 31 : set_action_from_name(argv[i] + 2);
3024 [ - + ]: 31 : if (MISSING_ARGS(1))
3025 : 0 : badusage(_("--%s needs <name>"), argv[i] + 2);
3026 : 31 : a = alternative_new(argv[i + 1]);
3027 : :
3028 : 31 : alternative_check_name(a->master_name);
3029 : :
3030 : 31 : i++;
3031 [ + - ]: 422 : } else if (strcmp("--all", argv[i]) == 0 ||
3032 [ + + ]: 422 : strcmp("--get-selections", argv[i]) == 0 ||
3033 [ + + ]: 407 : strcmp("--set-selections", argv[i]) == 0) {
3034 : 17 : set_action_from_name(argv[i] + 2);
3035 [ + + ]: 405 : } else if (strcmp("--slave", argv[i]) == 0) {
3036 : : const char *slink, *sname, *spath;
3037 : : struct slave_link *sl;
3038 : :
3039 [ - + ]: 93 : if (action != ACTION_INSTALL)
3040 : 0 : badusage(_("--%s only allowed with --%s"),
3041 : 0 : argv[i] + 2, "install");
3042 [ - + ]: 93 : if (MISSING_ARGS(3))
3043 : 0 : badusage(_("--%s needs <link> <name> <path>"),
3044 : 0 : argv[i] + 2);
3045 : :
3046 : 93 : slink = argv[i + 1];
3047 : 93 : sname = argv[i + 2];
3048 : 93 : spath = argv[i + 3];
3049 : :
3050 [ - + ]: 93 : if (strcmp(slink, spath) == 0)
3051 : 0 : badusage(_("<link> '%s' is the same as <path>"),
3052 : : slink);
3053 [ - + ]: 93 : if (strcmp(inst_alt->master_name, sname) == 0)
3054 : 0 : badusage(_("<name> '%s' is both primary and slave"),
3055 : : sname);
3056 [ + + ]: 93 : if (strcmp(slink, inst_alt->master_link) == 0)
3057 : 1 : badusage(_("<link> '%s' is both primary and slave"),
3058 : : slink);
3059 [ - + ]: 92 : if (alternative_has_slave(inst_alt, sname))
3060 : 0 : badusage(_("duplicate slave <name> '%s'"), sname);
3061 : :
3062 [ + + ]: 203 : for (sl = inst_alt->slaves; sl; sl = sl->next) {
3063 : 111 : const char *linkname = sl->link;
3064 [ - + ]: 111 : if (linkname == NULL)
3065 : 0 : linkname = "";
3066 [ - + ]: 111 : if (strcmp(linkname, slink) == 0)
3067 : 0 : badusage(_("duplicate slave <link> '%s'"),
3068 : : slink);
3069 : : }
3070 : :
3071 : 92 : alternative_add_slave(inst_alt, sname, slink);
3072 : 92 : fileset_add_slave(fileset, sname, spath);
3073 : :
3074 : 92 : i+= 3;
3075 [ + + ]: 312 : } else if (strcmp("--log", argv[i]) == 0) {
3076 [ - + ]: 107 : if (MISSING_ARGS(1))
3077 : 0 : badusage(_("--%s needs a <file> argument"),
3078 : 0 : argv[i] + 2);
3079 : 107 : free(log_file);
3080 : 107 : log_file = fsys_get_path(argv[i + 1]);
3081 : 107 : i++;
3082 [ + + ]: 205 : } else if (strcmp("--altdir", argv[i]) == 0) {
3083 [ - + ]: 92 : if (MISSING_ARGS(1))
3084 : 0 : badusage(_("--%s needs a <directory> argument"),
3085 : 0 : argv[i] + 2);
3086 : 92 : altdir = argv[i + 1];
3087 : 92 : i++;
3088 : :
3089 : : /* If altdir is below instdir, convert it to a relative
3090 : : * path, as we will prepend instdir as needed. */
3091 [ + - ]: 92 : if (strncmp(altdir, instdir, instdir_len) == 0)
3092 : 92 : altdir += instdir_len;
3093 [ + + ]: 113 : } else if (strcmp("--admindir", argv[i]) == 0) {
3094 [ - + ]: 101 : if (MISSING_ARGS(1))
3095 : 0 : badusage(_("--%s needs a <directory> argument"),
3096 : 0 : argv[i] + 2);
3097 : 101 : free(admdir);
3098 : 101 : admdir = xstrdup(argv[i + 1]);
3099 : 101 : i++;
3100 [ - + ]: 12 : } else if (strcmp("--instdir", argv[i]) == 0) {
3101 [ # # ]: 0 : if (MISSING_ARGS(1))
3102 : 0 : badusage(_("--%s needs a <directory> argument"),
3103 : 0 : argv[i] + 2);
3104 : 0 : fsys_set_dir(argv[i + 1]);
3105 : 0 : i++;
3106 : :
3107 : : /* If altdir is below instdir, convert it to a relative
3108 : : * path, as we will prepend instdir as needed. */
3109 [ # # ]: 0 : if (strncmp(altdir, instdir, instdir_len) == 0)
3110 : 0 : altdir += instdir_len;
3111 [ + + ]: 12 : } else if (strcmp("--root", argv[i]) == 0) {
3112 [ - + ]: 9 : if (MISSING_ARGS(1))
3113 : 0 : badusage(_("--%s needs a <directory> argument"),
3114 : 0 : argv[i] + 2);
3115 : 9 : set_rootdir(argv[i + 1]);
3116 : 9 : i++;
3117 [ - + ]: 3 : } else if (strcmp("--skip-auto", argv[i]) == 0) {
3118 : 0 : opt_skip_auto = 1;
3119 [ + - ]: 3 : } else if (strcmp("--force", argv[i]) == 0) {
3120 : 3 : opt_force = 1;
3121 : : } else {
3122 : 0 : badusage(_("unknown option '%s'"), argv[i]);
3123 : : }
3124 : : }
3125 : :
3126 [ - + ]: 106 : if (action == ACTION_NONE)
3127 : 0 : badusage(_("need --%s, --%s, --%s, --%s, --%s, --%s, --%s, "
3128 : : "--%s, --%s, --%s, --%s or --%s"),
3129 : : "display", "query", "list", "get-selections",
3130 : : "config", "set", "set-selections", "install",
3131 : : "remove", "all", "remove-all", "auto");
3132 : :
3133 : 106 : debug("root=%s admdir=%s altdir=%s", instdir, admdir, altdir);
3134 : :
3135 : : /* The following actions might modify the current alternative. */
3136 [ + + ]: 106 : if (action == ACTION_SET ||
3137 [ + - ]: 100 : action == ACTION_AUTO ||
3138 [ + + ]: 100 : action == ACTION_CONFIG ||
3139 [ + + ]: 97 : action == ACTION_REMOVE ||
3140 [ + + ]: 89 : action == ACTION_REMOVE_ALL ||
3141 [ + + ]: 87 : action == ACTION_INSTALL)
3142 : 63 : modifies_alt = true;
3143 : :
3144 : : /* The following actions might modify the system somehow. */
3145 [ + + ]: 106 : if (modifies_alt ||
3146 [ + - ]: 43 : action == ACTION_CONFIG_ALL ||
3147 [ + + ]: 43 : action == ACTION_SET_SELECTIONS)
3148 : 65 : modifies_sys = true;
3149 : :
3150 [ + + ]: 106 : if (action == ACTION_INSTALL)
3151 : 44 : alternative_check_install_args(inst_alt, fileset);
3152 : :
3153 [ + - ]: 96 : if (action == ACTION_DISPLAY ||
3154 [ + + ]: 96 : action == ACTION_QUERY ||
3155 [ + - ]: 70 : action == ACTION_LIST ||
3156 [ + + ]: 70 : action == ACTION_SET ||
3157 [ + - ]: 64 : action == ACTION_AUTO ||
3158 [ + + ]: 64 : action == ACTION_CONFIG ||
3159 [ + + ]: 61 : action == ACTION_REMOVE_ALL) {
3160 : : /* Load the alternative info, stop on failure. */
3161 [ + + ]: 37 : if (!alternative_load(a, ALTDB_WARN_PARSER))
3162 : 3 : error(_("no alternatives for %s"), a->master_name);
3163 [ + + ]: 59 : } else if (action == ACTION_REMOVE) {
3164 : : /* XXX: Be consistent for now with the case when we
3165 : : * try to remove a non-existing path from an existing
3166 : : * link group file. */
3167 [ + + ]: 8 : if (!alternative_load(a, ALTDB_WARN_PARSER)) {
3168 : 1 : verbose(_("no alternatives for %s"), a->master_name);
3169 : 1 : alternative_free(a);
3170 : 1 : free(log_file);
3171 : 1 : free(admdir);
3172 : 1 : exit(0);
3173 : : }
3174 [ + + ]: 51 : } else if (action == ACTION_INSTALL) {
3175 : : /* Load the alternative info, ignore failures. */
3176 : 34 : alternative_load(a, ALTDB_WARN_PARSER);
3177 : : }
3178 : :
3179 [ + + ]: 91 : if (modifies_sys)
3180 : 53 : log_msg("run with %s", get_argv_string(argc, argv));
3181 : :
3182 [ + + ]: 91 : if (modifies_alt) {
3183 : 51 : current_choice = alternative_get_current(a);
3184 : 51 : alternative_select_mode(a, current_choice);
3185 : : }
3186 : :
3187 : : /* Handle actions. */
3188 [ - + ]: 91 : if (action == ACTION_CONFIG_ALL) {
3189 : 0 : alternative_config_all();
3190 [ + + ]: 91 : } else if (action == ACTION_GET_SELECTIONS) {
3191 : 15 : alternative_get_selections();
3192 [ + + ]: 76 : } else if (action == ACTION_SET_SELECTIONS) {
3193 : 2 : alternative_set_selections(stdin, _("<standard input>"));
3194 [ - + ]: 74 : } else if (action == ACTION_DISPLAY) {
3195 : 0 : alternative_display_user(a);
3196 [ + + ]: 74 : } else if (action == ACTION_QUERY) {
3197 : 23 : alternative_display_query(a);
3198 [ - + ]: 51 : } else if (action == ACTION_LIST) {
3199 : 0 : alternative_display_list(a);
3200 [ + + ]: 51 : } else if (action == ACTION_SET) {
3201 : 6 : new_choice = alternative_set_manual(a, path);
3202 [ - + ]: 45 : } else if (action == ACTION_AUTO) {
3203 : 0 : new_choice = alternative_set_auto(a);
3204 [ + + ]: 45 : } else if (action == ACTION_CONFIG) {
3205 : 3 : new_choice = alternative_config(a, current_choice);
3206 [ + + ]: 42 : } else if (action == ACTION_REMOVE) {
3207 : 7 : new_choice = alternative_remove(a, current_choice, path);
3208 [ + + ]: 35 : } else if (action == ACTION_REMOVE_ALL) {
3209 : 2 : alternative_choices_free(a);
3210 [ + - ]: 33 : } else if (action == ACTION_INSTALL) {
3211 : 33 : new_choice = alternative_install(&a, inst_alt, current_choice,
3212 : : fileset);
3213 : : }
3214 : :
3215 [ + + ]: 91 : if (modifies_alt)
3216 : 51 : alternative_update(a, current_choice, new_choice);
3217 : :
3218 [ + + ]: 89 : if (a)
3219 : 72 : alternative_free(a);
3220 : 89 : free(new_choice);
3221 : 89 : free(log_file);
3222 : 89 : free(admdir);
3223 : :
3224 : 89 : return 0;
3225 : : }
|