Dpkg::Vendor::Debian: Move time64 buildflags feature from future to abi
[dpkg.git] / utils / update-alternatives.c
blob77a1c5598506b87d715a7f37985061ef365e7671
1 /*
2 * update-alternatives
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>
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.
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.
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/>.
24 #include <config.h>
25 #include <compat.h>
27 #include <sys/types.h>
28 #include <sys/time.h>
29 #include <sys/stat.h>
30 #include <sys/wait.h>
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>
47 #include <dpkg/macros.h>
48 #include <dpkg/i18n.h>
50 /* Global variables: */
52 #define PROGNAME "update-alternatives"
54 static const char *altdir = SYSCONFDIR "/alternatives";
55 static char *admdir = NULL;
56 static const char *instdir = "";
57 static size_t instdir_len;
59 static const char *prog_path = "update-alternatives";
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,
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" },
96 enum output_mode {
97 OUTPUT_QUIET = -1,
98 OUTPUT_NORMAL = 0,
99 OUTPUT_VERBOSE = 1,
100 OUTPUT_DEBUG = 2,
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;
113 * Functions.
116 static void
117 version(void)
119 printf(_("Debian %s version %s.\n"), PROGNAME, VERSION);
120 printf("\n");
122 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"));
127 static void
128 usage(void)
130 printf(_(
131 "Usage: %s [<option> ...] <command>\n"
132 "\n"), PROGNAME);
134 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"));
153 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);
164 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);
184 static void LIBCOMPAT_ATTR_NORET LIBCOMPAT_ATTR_PRINTF(1)
185 error(char const *fmt, ...)
187 va_list args;
189 fprintf(stderr, "%s: %s: ", PROGNAME, _("error"));
190 va_start(args, fmt);
191 vfprintf(stderr, fmt, args);
192 va_end(args);
193 fprintf(stderr, "\n");
194 exit(2);
197 static void LIBCOMPAT_ATTR_NORET LIBCOMPAT_ATTR_PRINTF(1)
198 syserr(char const *fmt, ...)
200 va_list args;
202 fprintf(stderr, "%s: %s: ", PROGNAME, _("error"));
203 va_start(args, fmt);
204 vfprintf(stderr, fmt, args);
205 va_end(args);
206 fprintf(stderr, ": %s\n", strerror(errno));
207 exit(2);
210 static void LIBCOMPAT_ATTR_NORET LIBCOMPAT_ATTR_PRINTF(1)
211 badusage(char const *fmt, ...)
213 va_list args;
215 fprintf(stderr, "%s: ", PROGNAME);
216 va_start(args, fmt);
217 vfprintf(stderr, fmt, args);
218 va_end(args);
219 fprintf(stderr, "\n\n");
220 fprintf(stderr, _("Use '%s --help' for program usage information."),
221 PROGNAME);
222 fprintf(stderr, "\n");
223 exit(2);
226 static void LIBCOMPAT_ATTR_PRINTF(1)
227 warning(char const *fmt, ...)
229 va_list args;
231 if (opt_verbose < OUTPUT_NORMAL)
232 return;
234 fprintf(stderr, "%s: %s: ", PROGNAME, _("warning"));
235 va_start(args, fmt);
236 vfprintf(stderr, fmt, args);
237 va_end(args);
238 fprintf(stderr, "\n");
241 static void LIBCOMPAT_ATTR_PRINTF(1)
242 debug(char const *fmt, ...)
244 va_list args;
246 if (opt_verbose < OUTPUT_DEBUG)
247 return;
249 fprintf(stderr, "DEBUG: ");
250 va_start(args, fmt);
251 vfprintf(stderr, fmt, args);
252 va_end(args);
253 fprintf(stderr, "\n");
256 static void LIBCOMPAT_ATTR_PRINTF(1)
257 verbose(char const *fmt, ...)
259 va_list args;
261 if (opt_verbose < OUTPUT_VERBOSE)
262 return;
264 printf("%s: ", PROGNAME);
265 va_start(args, fmt);
266 vprintf(fmt, args);
267 va_end(args);
268 printf("\n");
271 static void LIBCOMPAT_ATTR_PRINTF(1)
272 info(char const *fmt, ...)
274 va_list args;
276 if (opt_verbose < OUTPUT_NORMAL)
277 return;
279 printf("%s: ", PROGNAME);
280 va_start(args, fmt);
281 vprintf(fmt, args);
282 va_end(args);
283 printf("\n");
286 static void LIBCOMPAT_ATTR_PRINTF(1)
287 pr(char const *fmt, ...)
289 va_list args;
291 va_start(args, fmt);
292 vprintf(fmt, args);
293 va_end(args);
294 printf("\n");
297 static void *
298 xmalloc(size_t size)
300 void *ptr;
302 ptr = malloc(size);
303 if (!ptr)
304 error(_("malloc failed (%zu bytes)"), size);
306 return ptr;
309 static char *
310 xstrdup(const char *str)
312 char *new_str;
314 if (!str)
315 return NULL;
317 new_str = strdup(str);
318 if (!new_str)
319 error(_("failed to allocate memory"));
321 return new_str;
324 static char *
325 xstrndup(const char *str, size_t n)
327 char *new_str;
329 if (!str)
330 return NULL;
332 new_str = strndup(str, n);
333 if (!new_str)
334 error(_("failed to allocate memory"));
336 return new_str;
339 static char * LIBCOMPAT_ATTR_VPRINTF(1)
340 xvasprintf(const char *fmt, va_list args)
342 char *str;
344 if (vasprintf(&str, fmt, args) < 0)
345 error(_("failed to allocate memory"));
347 return str;
350 static char * LIBCOMPAT_ATTR_PRINTF(1)
351 xasprintf(const char *fmt, ...)
353 va_list args;
354 char *str;
356 va_start(args, fmt);
357 str = xvasprintf(fmt, args);
358 va_end(args);
360 return str;
363 static char *
364 areadlink(const char *linkname)
366 struct stat st;
367 char *buf;
368 ssize_t size;
370 /* Allocate required memory to store the value of the symlink */
371 if (lstat(linkname, &st))
372 return NULL;
374 if (!S_ISLNK(st.st_mode)) {
375 errno = EINVAL;
376 return NULL;
379 buf = xmalloc(st.st_size + 1);
381 /* Read it and terminate the string properly */
382 size = readlink(linkname, buf, st.st_size);
383 if (size == -1) {
384 int saved_errno = errno;
386 free(buf);
387 errno = saved_errno;
389 return NULL;
391 buf[size] = '\0';
393 return buf;
396 static int
397 spawn(const char *prog, const char *args[])
399 pid_t pid, dead_pid;
400 int status;
402 pid = fork();
403 if (pid == -1)
404 error(_("fork failed"));
405 if (pid == 0) {
406 execvp(prog, (char *const *)args);
407 syserr(_("unable to execute %s (%s)"), prog, prog);
409 while ((dead_pid = waitpid(pid, &status, 0)) == -1 && errno == EINTR) ;
410 if (dead_pid != pid)
411 error(_("wait for subprocess %s failed"), prog);
413 return status;
416 static bool
417 rename_mv(const char *src, const char *dst)
419 const char *args[] = { "mv", src, dst, NULL };
420 int rc;
422 if (rename(src, dst) == 0)
423 return true;
424 if (errno == ENOENT)
425 return false;
427 rc = spawn("mv", args);
428 if (WIFEXITED(rc) && WEXITSTATUS(rc) == 0)
429 return true;
431 return false;
434 static void
435 xrename(const char *src, const char *dst)
437 if (!rename_mv(src, dst))
438 syserr(_("unable to install '%.250s' as '%.250s'"), src, dst);
441 static void LIBCOMPAT_ATTR_PRINTF(1)
442 xunlink_args(const char *fmt, ...)
444 va_list args;
445 char *path;
447 va_start(args, fmt);
448 path = xvasprintf(fmt, args);
449 va_end(args);
451 if (unlink(path) < 0 && errno != ENOENT)
452 syserr(_("unable to remove '%s'"), path);
454 free(path);
457 static char *
458 xdirname(const char *pathname)
460 char *dirname, *slash;
462 slash = strrchr(pathname, '/');
463 if (slash)
464 dirname = xstrndup(pathname, slash - pathname);
465 else
466 dirname = xstrdup(".");
468 return dirname;
471 static int
472 make_path(const char *pathname, mode_t mode)
474 char *dirname, *slash;
476 dirname = xstrdup(pathname);
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 slash = strchr(dirname, '/');
483 while (slash != NULL) {
484 slash = strchr(slash + 1, '/');
485 if (slash)
486 *slash = '\0';
488 if (mkdir(dirname, mode) < 0 && errno != EEXIST) {
489 free(dirname);
490 return -1;
492 if (slash)
493 *slash = '/';
496 free(dirname);
498 return 0;
501 static void LIBCOMPAT_ATTR_PRINTF(1)
502 log_msg(const char *fmt, ...)
504 va_list args;
506 if (fh_log == NULL) {
507 fh_log = fopen(log_file, "a");
508 if (fh_log == NULL && errno == ENOENT) {
509 char *log_dir = xdirname(log_file);
511 if (make_path(log_dir, 0755) < 0)
512 syserr(_("cannot create log directory '%s'"),
513 log_dir);
514 free(log_dir);
516 fh_log = fopen(log_file, "a");
518 if (fh_log == NULL && errno != EACCES)
519 syserr(_("cannot append to '%s'"), log_file);
522 if (fh_log) {
523 char timestamp[64];
524 time_t now;
525 struct tm tm;
527 time(&now);
528 if (localtime_r(&now, &tm) == NULL)
529 syserr(_("cannot get local time to log into '%s'"), log_file);
530 strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S",
531 &tm);
532 fprintf(fh_log, "%s %s: ", PROGNAME, timestamp);
533 va_start(args, fmt);
534 vfprintf(fh_log, fmt, args);
535 va_end(args);
536 fprintf(fh_log, "\n");
541 * Filesystem access for alernative handling.
544 static char *
545 fsys_get_path(const char *pathpart)
547 return xasprintf("%s%s", instdir, pathpart);
550 static const char *
551 fsys_set_dir(const char *dir)
553 if (dir == NULL) {
554 const char *instdir_env;
556 instdir_env = getenv(INSTDIR_ENVVAR);
557 if (instdir_env)
558 dir = instdir_env;
559 else
560 dir = "";
563 instdir_len = strlen(dir);
565 return dir;
568 static char *
569 fsys_gen_admindir(void)
571 return fsys_get_path(ADMINDIR "/alternatives");
574 static bool
575 fsys_pathname_is_missing(const char *pathname)
577 struct stat st;
578 char *root_pathname;
580 root_pathname = fsys_get_path(pathname);
582 errno = 0;
583 if (stat(root_pathname, &st) < 0 && errno != ENOENT)
584 syserr(_("cannot stat file '%s'"), root_pathname);
586 free(root_pathname);
588 if (errno == ENOENT)
589 return true;
591 return false;
594 static int
595 fsys_lstat(const char *linkname, struct stat *st)
597 char *root_linkname;
598 int rc;
600 root_linkname = fsys_get_path(linkname);
602 errno = 0;
603 rc = lstat(root_linkname, st);
605 free(root_linkname);
607 return rc;
610 static char *
611 fsys_areadlink(const char *linkname)
613 char *root_linkname;
614 char *target;
616 root_linkname = fsys_get_path(linkname);
617 target = areadlink(root_linkname);
618 free(root_linkname);
620 return target;
623 static char *
624 fsys_xreadlink(const char *linkname)
626 char *buf;
628 buf = fsys_areadlink(linkname);
629 if (buf == NULL)
630 syserr(_("unable to read link '%s%.255s'"), instdir, linkname);
632 return buf;
635 static void
636 fsys_set_ref_time(const char *linkname, const char *target)
638 #ifdef HAVE_LUTIMES
639 /* If the symlink did not exist, then copy the timestamps
640 * from the target. This is needed so we can get reproducible
641 * installations, for programs that track these timestamps on
642 * their databases. */
643 struct stat st;
644 struct timeval tv[2];
645 char *root_linkname;
647 if (fsys_lstat(target, &st) < 0) {
648 if (errno != ENOENT)
649 syserr(_("unable to get file '%s%s' metadata"),
650 instdir, target);
651 return;
654 tv[0].tv_sec = st.st_mtime;
655 tv[0].tv_usec = 0;
656 tv[1].tv_sec = st.st_mtime;
657 tv[1].tv_usec = 0;
659 root_linkname = fsys_get_path(linkname);
660 if (lutimes(root_linkname, tv) < 0 && errno != ENOSYS)
661 syserr(_("cannot set symlink '%s' timestamp"), root_linkname);
662 free(root_linkname);
663 #endif
666 static void
667 fsys_symlink(const char *filename, const char *linkname)
669 char *root_linkname;
671 root_linkname = fsys_get_path(linkname);
673 if (unlink(root_linkname) < 0 && errno != ENOENT)
674 syserr(_("unable to remove '%s'"), root_linkname);
676 if (symlink(filename, root_linkname))
677 syserr(_("error creating symbolic link '%.255s'"), root_linkname);
679 free(root_linkname);
682 static void
683 fsys_mv(const char *src, const char *dst)
685 char *root_src;
686 char *root_dst;
688 root_src = fsys_get_path(src);
689 root_dst = fsys_get_path(dst);
691 xrename(root_src, root_dst);
693 free(root_src);
694 free(root_dst);
697 static void
698 fsys_rm(const char *f)
700 char *root_f;
702 root_f = fsys_get_path(f);
704 if (unlink(root_f) < 0 && errno != ENOENT)
705 syserr(_("unable to remove '%s'"), root_f);
707 free(root_f);
710 static void LIBCOMPAT_ATTR_PRINTF(1)
711 fsys_rm_args(const char *fmt, ...)
713 va_list args;
714 char *path;
716 va_start(args, fmt);
717 path = xvasprintf(fmt, args);
718 va_end(args);
720 fsys_rm(path);
721 free(path);
725 * OBJECTS
728 struct fileset {
729 struct fileset *next;
731 char *master_file;
732 int priority;
734 struct slave_file {
735 struct slave_file *next;
736 char *name;
737 char *file;
738 } *slaves;
741 static struct fileset *
742 fileset_new(const char *master_file, int prio)
744 struct fileset *fs;
746 fs = xmalloc(sizeof(*fs));
747 fs->next = NULL;
748 fs->master_file = xstrdup(master_file);
749 fs->priority = prio;
750 fs->slaves = NULL;
752 return fs;
755 static void
756 fileset_free(struct fileset *fs)
758 struct slave_file *slave, *next;
760 free(fs->master_file);
761 for (slave = fs->slaves; slave; slave = next) {
762 next = slave->next;
763 free(slave->name);
764 free(slave->file);
765 free(slave);
767 free(fs);
770 static void
771 fileset_add_slave(struct fileset *fs, const char *name, const char *file)
773 struct slave_file *sl, *cur, *prev = NULL;
775 /* Replace existing first */
776 for (cur = fs->slaves; cur; cur = cur->next) {
777 if (strcmp(cur->name, name) == 0) {
778 free(cur->file);
779 cur->file = xstrdup(file);
780 return;
782 prev = cur;
785 /* Otherwise add new at the end */
786 sl = xmalloc(sizeof(*sl));
787 sl->next = NULL;
788 sl->name = xstrdup(name);
789 sl->file = xstrdup(file);
790 if (prev)
791 prev->next = sl;
792 else
793 fs->slaves = sl;
796 static const char *
797 fileset_get_slave(struct fileset *fs, const char *name)
799 struct slave_file *slave;
801 for (slave = fs->slaves; slave; slave = slave->next) {
802 if (strcmp(slave->name, name) == 0)
803 return slave->file;
806 return NULL;
809 static bool
810 fileset_has_slave(struct fileset *fs, const char *name)
812 const char *file = fileset_get_slave(fs, name);
814 if (file == NULL)
815 return false;
817 return file[0] != '\0';
820 static bool
821 fileset_can_install_slave(struct fileset *fs, const char *slave_name)
823 /* Decide whether the slave alternative must be setup */
824 if (fileset_has_slave(fs, slave_name)) {
825 const char *slave = fileset_get_slave(fs, slave_name);
827 if (!fsys_pathname_is_missing(slave))
828 return true;
831 return false;
834 struct slave_link {
835 struct slave_link *next;
836 char *name;
837 char *link;
838 bool updated;
841 struct commit_operation {
842 struct commit_operation *next;
844 enum opcode {
845 OPCODE_NOP,
846 OPCODE_RM,
847 OPCODE_MV,
848 OPCODE_REF_TIME,
849 } opcode;
851 char *arg_a;
852 char *arg_b;
855 enum alternative_update_reason {
856 ALT_UPDATE_NO,
857 ALT_UPDATE_SLAVE_CHANGED,
858 ALT_UPDATE_LINK_BROKEN,
861 struct alternative {
862 char *master_name;
863 char *master_link;
864 char *current;
866 enum alternative_status {
867 ALT_ST_UNKNOWN,
868 ALT_ST_AUTO,
869 ALT_ST_MANUAL,
870 } status;
872 struct slave_link *slaves;
873 struct fileset *choices;
875 struct commit_operation *commit_ops;
877 int ref_count;
878 bool modified;
879 bool known_current;
882 static void
883 slave_link_free(struct slave_link *slave)
885 free(slave->name);
886 free(slave->link);
887 free(slave);
890 static void
891 commit_operation_free(struct commit_operation *commit_op)
893 free(commit_op->arg_a);
894 free(commit_op->arg_b);
895 free(commit_op);
898 static struct alternative *
899 alternative_new(const char *name)
901 struct alternative *alt;
903 alt = xmalloc(sizeof(*alt));
904 alt->master_name = xstrdup(name);
905 alt->master_link = NULL;
906 alt->current = NULL;
907 alt->status = ALT_ST_UNKNOWN;
908 alt->slaves = NULL;
909 alt->choices = NULL;
910 alt->commit_ops = NULL;
911 alt->modified = false;
912 alt->known_current = false;
913 alt->ref_count = 1;
915 return alt;
918 static inline void
919 alternative_ref(struct alternative *a)
921 a->ref_count++;
924 static inline bool
925 alternative_unref(struct alternative *a)
927 return --a->ref_count == 0;
930 static void
931 alternative_choices_free(struct alternative *a)
933 struct fileset *fs;
935 if (a->choices)
936 a->modified = true;
938 while (a->choices) {
939 fs = a->choices;
940 a->choices = fs->next;
941 fileset_free(fs);
945 static void
946 alternative_commit_operations_free(struct alternative *a)
948 struct commit_operation *op;
950 while (a->commit_ops) {
951 op = a->commit_ops;
952 a->commit_ops = op->next;
953 commit_operation_free(op);
957 static void
958 alternative_reset(struct alternative *alt)
960 struct slave_link *slave;
962 free(alt->current);
963 alt->current = NULL;
964 free(alt->master_link);
965 alt->master_link = NULL;
966 while (alt->slaves) {
967 slave = alt->slaves;
968 alt->slaves = slave->next;
969 slave_link_free(slave);
971 alternative_choices_free(alt);
972 alternative_commit_operations_free(alt);
973 alt->modified = false;
974 alt->known_current = false;
977 static void
978 alternative_free(struct alternative *alt)
980 if (!alternative_unref(alt))
981 return;
983 alternative_reset(alt);
984 free(alt->master_name);
985 free(alt);
988 static int
989 alternative_choices_count(struct alternative *alt)
991 struct fileset *fs;
992 int count = 0;
994 for (fs = alt->choices; fs; fs = fs->next)
995 count++;
997 return count;
1000 static int
1001 alternative_slaves_count(struct alternative *alt)
1003 struct slave_link *sl;
1004 int count = 0;
1006 for (sl = alt->slaves; sl; sl = sl->next)
1007 count++;
1009 return count;
1012 static int
1013 compare_fileset(const void *va, const void *vb)
1015 const struct fileset *a = *(const struct fileset **)va;
1016 const struct fileset *b = *(const struct fileset **)vb;
1018 assert(a && a->master_file);
1019 assert(b && b->master_file);
1021 return strcmp(a->master_file, b->master_file);
1024 static int
1025 compare_slave_link(const void *va, const void *vb)
1027 const struct slave_link *a = *(const struct slave_link **)va;
1028 const struct slave_link *b = *(const struct slave_link **)vb;
1030 assert(a && a->name);
1031 assert(b && b->name);
1033 return strcmp(a->name, b->name);
1036 static void
1037 alternative_sort_choices(struct alternative *a)
1039 int count, i;
1040 struct fileset **table, *fs;
1042 count = alternative_choices_count(a);
1043 if (count < 2) /* Nothing to sort */
1044 return;
1046 /* Store objects in a table instead of a linked list */
1047 table = xmalloc(sizeof(fs) * count);
1048 for (fs = a->choices, i = 0; fs; fs = fs->next) {
1049 assert(fs->master_file);
1050 table[i++] = fs;
1053 qsort(table, count, sizeof(fs), compare_fileset);
1055 /* Rewrite the linked list from the sorted table */
1056 a->choices = fs = table[0];
1057 table[count - 1]->next = NULL;
1058 for (i = 1; i < count; fs = fs->next, i++)
1059 fs->next = table[i];
1060 free(table);
1063 static void
1064 alternative_sort_slaves(struct alternative *a)
1066 int count, i;
1067 struct slave_link **table, *sl;
1069 count = alternative_slaves_count(a);
1070 if (count < 2) /* Nothing to sort */
1071 return;
1073 /* Store objects in a table instead of a linked list */
1074 table = xmalloc(sizeof(sl) * count);
1075 for (sl = a->slaves, i = 0; sl; sl = sl->next, i++) {
1076 table[i] = sl;
1079 qsort(table, count, sizeof(sl), compare_slave_link);
1081 /* Rewrite the linked list from the sorted table */
1082 a->slaves = sl = table[0];
1083 table[count - 1]->next = NULL;
1084 for (i = 1; i < count; sl = sl->next, i++)
1085 sl->next = table[i];
1086 free(table);
1089 static struct fileset *
1090 alternative_get_fileset(struct alternative *a, const char *file)
1092 struct fileset *fs;
1094 for (fs = a->choices; fs; fs = fs->next)
1095 if (strcmp(fs->master_file, file) == 0)
1096 return fs;
1098 return NULL;
1101 static struct slave_link *
1102 alternative_get_slave(struct alternative *a, const char *name)
1104 struct slave_link *sl;
1106 for (sl = a->slaves; sl; sl = sl->next)
1107 if (strcmp(sl->name, name) == 0)
1108 return sl;
1110 return NULL;
1113 static bool
1114 alternative_has_slave(struct alternative *a, const char *name)
1116 return alternative_get_slave(a, name) != NULL;
1119 static bool
1120 alternative_has_choice(struct alternative *a, const char *file)
1122 return alternative_get_fileset(a, file) != NULL;
1125 static void
1126 alternative_add_choice(struct alternative *a, struct fileset *fs)
1128 struct fileset *cur, *prev = NULL;
1130 /* Replace if already existing */
1131 for (cur = a->choices; cur; cur = cur->next) {
1132 if (strcmp(cur->master_file, fs->master_file) == 0) {
1133 fs->next = cur->next;
1134 fileset_free(cur);
1135 if (prev)
1136 prev->next = fs;
1137 else
1138 a->choices = fs;
1140 /* XXX: Be smarter in detecting change? */
1141 a->modified = true;
1142 return;
1144 prev = cur;
1147 /* Otherwise add at the end */
1148 if (prev == NULL)
1149 a->choices = fs;
1150 else
1151 prev->next = fs;
1152 fs->next = NULL;
1153 a->modified = true;
1156 static struct slave_link *
1157 alternative_add_slave(struct alternative *a,
1158 const char *slave_name, const char *slave_link)
1160 struct slave_link *sl, *new;
1162 /* Replace if already existing */
1163 for (sl = a->slaves; sl; sl = sl->next) {
1164 if (strcmp(sl->name, slave_name) == 0) {
1165 free(sl->link);
1166 sl->link = xstrdup(slave_link);
1167 return sl;
1169 if (sl->next == NULL)
1170 break;
1173 /* Otherwise create new and add at the end */
1174 new = xmalloc(sizeof(*new));
1175 new->name = xstrdup(slave_name);
1176 new->link = xstrdup(slave_link);
1177 new->updated = false;
1178 new->next = NULL;
1179 if (sl)
1180 sl->next = new;
1181 else
1182 a->slaves = new;
1184 return new;
1187 static void
1188 alternative_copy_slave(struct alternative *a, struct slave_link *sl)
1190 struct slave_link *sl_new;
1192 sl_new = alternative_add_slave(a, sl->name, sl->link);
1193 sl_new->updated = sl->updated;
1196 static const char *
1197 alternative_status_string(enum alternative_status status)
1199 return (status == ALT_ST_AUTO) ? "auto" : "manual";
1202 static const char *
1203 alternative_status_describe(enum alternative_status status)
1205 return (status == ALT_ST_AUTO) ? _("auto mode") : _("manual mode");
1208 static void
1209 alternative_set_status(struct alternative *a, enum alternative_status status)
1211 if (a->status == ALT_ST_UNKNOWN || status != a->status)
1212 a->modified = true;
1214 if (a->status != ALT_ST_UNKNOWN && status != a->status)
1215 log_msg("status of link group %s set to %s", a->master_link,
1216 alternative_status_string(status));
1218 a->status = status;
1221 static void
1222 alternative_set_link(struct alternative *a, const char *linkname)
1224 if (a->master_link == NULL || strcmp(linkname, a->master_link) != 0)
1225 a->modified = true;
1227 free(a->master_link);
1228 a->master_link = xstrdup(linkname);
1231 static bool
1232 alternative_remove_choice(struct alternative *a, const char *file)
1234 struct fileset *fs, *fs_prev;
1236 fs_prev = NULL;
1237 for (fs = a->choices; fs; fs = fs->next) {
1238 if (strcmp(fs->master_file, file) != 0) {
1239 fs_prev = fs;
1240 continue;
1242 if (fs_prev)
1243 fs_prev->next = fs->next;
1244 else
1245 a->choices = fs->next;
1246 fileset_free(fs);
1247 a->modified = true;
1248 return true;
1251 return false;
1255 * Alternatives Database Load/Store functions.
1258 enum LIBCOMPAT_ATTR_ENUM_FLAGS altdb_flags {
1259 ALTDB_LAX_PARSER = 1 << 0,
1260 ALTDB_WARN_PARSER = 1 << 1,
1263 struct altdb_context {
1264 FILE *fh;
1265 char *filename;
1266 enum altdb_flags flags;
1267 bool modified;
1268 void LIBCOMPAT_ATTR_NORET LIBCOMPAT_ATTR_PRINTF(2)
1269 (*bad_format)(struct altdb_context *, const char *format, ...);
1270 jmp_buf on_error;
1273 static void
1274 altdb_context_free(struct altdb_context *ctx)
1276 if (ctx->fh)
1277 fclose(ctx->fh);
1278 free(ctx->filename);
1281 static int
1282 altdb_filter_namelist(const struct dirent *entry)
1284 if (strcmp(entry->d_name, ".") == 0 ||
1285 strcmp(entry->d_name, "..") == 0 ||
1286 (strlen(entry->d_name) > strlen(ALT_TMP_EXT) &&
1287 strcmp(entry->d_name + strlen(entry->d_name) -
1288 strlen(ALT_TMP_EXT), ALT_TMP_EXT) == 0))
1289 return 0;
1290 return 1;
1293 static int
1294 altdb_get_namelist(struct dirent ***table)
1296 int count;
1298 count = scandir(admdir, table, altdb_filter_namelist, alphasort);
1299 if (count < 0) {
1300 if (errno != ENOENT)
1301 syserr(_("cannot scan directory '%.255s'"), admdir);
1302 /* The directory does not exist, proceed anyway. */
1303 *table = NULL;
1304 count = 0;
1307 return count;
1310 static void
1311 altdb_free_namelist(struct dirent **table, int n)
1313 while (n--)
1314 free(table[n]);
1315 free(table);
1318 static char *
1319 altdb_get_line(struct altdb_context *ctx, const char *name)
1321 char *buf, *line;
1322 size_t len, bufsz, i;
1324 bufsz = 1024;
1325 buf = xmalloc(bufsz);
1327 for (i = 0; true; i += strlen(line)) {
1328 errno = 0;
1329 line = fgets(buf + i, bufsz - i, ctx->fh);
1330 if (line) {
1331 if (strlen(buf) < bufsz - 1 || buf[bufsz - 2] == '\n')
1332 break;
1333 /* Need more space */
1334 bufsz *= 2;
1335 buf = realloc(buf, bufsz);
1336 if (!buf)
1337 error(_("failed to allocate memory"));
1338 continue;
1340 if (feof(ctx->fh))
1341 ctx->bad_format(ctx, _("unexpected end of file while trying "
1342 "to read %s"), name);
1343 ctx->bad_format(ctx, _("while reading %s: %s"),
1344 name, strerror(errno));
1347 len = strlen(buf);
1348 if (len == 0 || buf[len - 1] != '\n') {
1349 ctx->bad_format(ctx, _("line not terminated while trying "
1350 "to read %s"), name);
1352 line[len - 1] = '\0';
1354 return buf;
1357 static void LIBCOMPAT_ATTR_NORET LIBCOMPAT_ATTR_PRINTF(2)
1358 altdb_parse_error(struct altdb_context *ctx, const char *format, ...)
1360 char *msg;
1361 va_list args;
1363 va_start(args, format);
1364 msg = xvasprintf(format, args);
1365 va_end(args);
1367 error(_("%s corrupt: %s"), ctx->filename, msg);
1370 static void LIBCOMPAT_ATTR_NORET LIBCOMPAT_ATTR_PRINTF(2)
1371 altdb_parse_stop(struct altdb_context *ctx, const char *format, ...)
1373 longjmp(ctx->on_error, 1);
1376 static void
1377 altdb_print_line(struct altdb_context *ctx, const char *line)
1379 if (strchr(line, '\n') != NULL)
1380 error(_("newlines prohibited in update-alternatives files (%s)"),
1381 line);
1383 if (fprintf(ctx->fh, "%s\n", line) < (int) strlen(line) + 1)
1384 syserr(_("unable to write file '%s'"), ctx->filename);
1387 static bool
1388 alternative_parse_slave(struct alternative *a, struct altdb_context *ctx)
1390 char *name, *linkname;
1391 struct slave_link *sl;
1393 name = altdb_get_line(ctx, _("slave name"));
1394 if (!strlen(name)) { /* End of list */
1395 free(name);
1396 return false;
1398 sl = alternative_get_slave(a, name);
1399 if (sl) {
1400 free(name);
1401 ctx->bad_format(ctx, _("duplicate slave name %s"), sl->name);
1404 linkname = altdb_get_line(ctx, _("slave link"));
1405 if (strcmp(linkname, a->master_link) == 0) {
1406 free(linkname);
1407 free(name);
1408 ctx->bad_format(ctx, _("slave link same as main link %s"),
1409 a->master_link);
1411 for (sl = a->slaves; sl; sl = sl->next) {
1412 if (strcmp(linkname, sl->link) == 0) {
1413 free(linkname);
1414 free(name);
1415 ctx->bad_format(ctx, _("duplicate slave link %s"),
1416 sl->link);
1420 alternative_add_slave(a, name, linkname);
1421 free(linkname);
1422 free(name);
1424 return true;
1427 static bool
1428 alternative_parse_fileset(struct alternative *a, struct altdb_context *ctx)
1430 struct fileset *fs;
1431 struct slave_link *sl;
1432 char *master_file;
1434 master_file = altdb_get_line(ctx, _("master file"));
1435 if (!strlen(master_file)) { /* End of list */
1436 free(master_file);
1437 return false;
1440 fs = alternative_get_fileset(a, master_file);
1441 if (fs)
1442 ctx->bad_format(ctx, _("duplicate path %s"), master_file);
1444 if (fsys_pathname_is_missing(master_file)) {
1445 char *junk;
1447 /* File not found - remove. */
1448 if (ctx->flags & ALTDB_WARN_PARSER)
1449 warning(_("alternative %s (part of link group %s) "
1450 "doesn't exist; removing from list of "
1451 "alternatives"), master_file, a->master_name);
1452 junk = altdb_get_line(ctx, _("priority"));
1453 free(junk);
1454 for (sl = a->slaves; sl; sl = sl->next) {
1455 junk = altdb_get_line(ctx, _("slave file"));
1456 free(junk);
1458 ctx->modified = true;
1459 } else {
1460 char *prio_str, *prio_end;
1461 long prio;
1463 prio_str = altdb_get_line(ctx, _("priority"));
1464 errno = 0;
1465 prio = strtol(prio_str, &prio_end, 10);
1466 /* XXX: Leak master_file/prio_str on non-fatal error */
1467 if (prio_str == prio_end || *prio_end != '\0')
1468 ctx->bad_format(ctx, _("priority of %s: %s"),
1469 master_file, prio_str);
1470 if (prio < INT_MIN || prio > INT_MAX || errno == ERANGE)
1471 ctx->bad_format(ctx,
1472 _("priority of %s is out of range: %s"),
1473 master_file, prio_str);
1474 free(prio_str);
1476 fs = fileset_new(master_file, prio);
1477 for (sl = a->slaves; sl; sl = sl->next) {
1478 char *slave_file = altdb_get_line(ctx, _("slave file"));
1479 fileset_add_slave(fs, sl->name, slave_file);
1480 free(slave_file);
1482 alternative_add_choice(a, fs);
1484 free(master_file);
1486 return true;
1489 static bool
1490 alternative_load(struct alternative *a, enum altdb_flags flags)
1492 struct altdb_context ctx;
1493 struct stat st;
1494 char *status;
1495 char *master_link;
1497 /* Initialize parse context */
1498 ctx.modified = false;
1499 ctx.flags = flags;
1500 if (flags & ALTDB_LAX_PARSER)
1501 ctx.bad_format = altdb_parse_stop;
1502 else
1503 ctx.bad_format = altdb_parse_error;
1504 ctx.filename = xasprintf("%s/%s", admdir, a->master_name);
1506 /* Open the alternative file. */
1507 ctx.fh = fopen(ctx.filename, "r");
1508 if (ctx.fh == NULL) {
1509 if (errno == ENOENT) {
1510 altdb_context_free(&ctx);
1511 return false;
1514 syserr(_("unable to open file '%s'"), ctx.filename);
1517 if (setjmp(ctx.on_error)) {
1518 altdb_context_free(&ctx);
1519 alternative_reset(a);
1520 return false;
1523 /* Verify the alternative is not empty. */
1524 if (fstat(fileno(ctx.fh), &st) == -1)
1525 syserr(_("cannot stat file '%s'"), ctx.filename);
1526 if (st.st_size == 0) {
1527 altdb_context_free(&ctx);
1528 alternative_reset(a);
1529 return false;
1532 /* Start parsing mandatory attributes (link+status) of the alternative */
1533 alternative_reset(a);
1534 status = altdb_get_line(&ctx, _("status"));
1535 if (strcmp(status, "auto") != 0 && strcmp(status, "manual") != 0)
1536 ctx.bad_format(&ctx, _("invalid status"));
1537 alternative_set_status(a, (strcmp(status, "auto") == 0) ?
1538 ALT_ST_AUTO : ALT_ST_MANUAL);
1539 free(status);
1541 master_link = altdb_get_line(&ctx, _("master link"));
1542 alternative_set_link(a, master_link);
1543 free(master_link);
1545 /* Parse the description of the slaves links of the alternative */
1546 while (alternative_parse_slave(a, &ctx));
1548 /* Parse the available choices in the alternative */
1549 while (alternative_parse_fileset(a, &ctx)) ;
1551 /* Close database file */
1552 if (fclose(ctx.fh))
1553 syserr(_("unable to close file '%s'"), ctx.filename);
1554 free(ctx.filename);
1556 /* Initialize the modified field which has been erroneously changed
1557 * by the various alternative_(add|set)_* calls:
1558 * false unless a choice has been auto-cleaned */
1559 a->modified = ctx.modified;
1561 return true;
1564 static void
1565 alternative_save(struct alternative *a)
1567 struct altdb_context ctx;
1568 struct slave_link *sl, *sl_prev;
1569 struct fileset *fs;
1570 char *filenew, *file;
1572 /* Cleanup unused slaves before writing admin file. */
1573 sl_prev = NULL;
1574 sl = a->slaves;
1575 while (sl) {
1576 bool has_slave = false;
1578 for (fs = a->choices; fs; fs = fs->next) {
1579 if (fileset_has_slave(fs, sl->name)) {
1580 has_slave = true;
1581 break;
1585 if (!has_slave) {
1586 struct slave_link *sl_rm;
1588 verbose(_("discarding obsolete slave link %s (%s)"),
1589 sl->name, sl->link);
1590 if (sl_prev)
1591 sl_prev->next = sl->next;
1592 else
1593 a->slaves = sl->next;
1594 sl_rm = sl;
1595 sl = sl->next;
1596 slave_link_free(sl_rm);
1597 } else {
1598 sl_prev = sl;
1599 sl = sl->next;
1603 /* Sort entries */
1604 alternative_sort_slaves(a);
1605 alternative_sort_choices(a);
1607 /* Write admin file. */
1608 file = xasprintf("%s/%s", admdir, a->master_name);
1609 filenew = xasprintf("%s" ALT_TMP_EXT, file);
1611 ctx.filename = filenew;
1612 ctx.fh = fopen(ctx.filename, "w");
1613 if (ctx.fh == NULL && errno == ENOENT) {
1614 if (make_path(admdir, 0755) < 0)
1615 syserr(_("cannot create administrative directory '%s'"),
1616 admdir);
1617 ctx.fh = fopen(ctx.filename, "w");
1619 if (ctx.fh == NULL)
1620 syserr(_("unable to create file '%s'"), ctx.filename);
1622 altdb_print_line(&ctx, alternative_status_string(a->status));
1623 altdb_print_line(&ctx, a->master_link);
1624 for (sl = a->slaves; sl; sl = sl->next) {
1625 altdb_print_line(&ctx, sl->name);
1626 altdb_print_line(&ctx, sl->link);
1628 altdb_print_line(&ctx, "");
1630 for (fs = a->choices; fs; fs = fs->next) {
1631 char *prio;
1633 altdb_print_line(&ctx, fs->master_file);
1635 prio = xasprintf("%d", fs->priority);
1636 altdb_print_line(&ctx, prio);
1637 free(prio);
1639 for (sl = a->slaves; sl; sl = sl->next) {
1640 if (fileset_has_slave(fs, sl->name))
1641 altdb_print_line(&ctx,
1642 fileset_get_slave(fs, sl->name));
1643 else
1644 altdb_print_line(&ctx, "");
1647 altdb_print_line(&ctx, "");
1649 /* Close database file */
1650 if (fflush(ctx.fh))
1651 syserr(_("unable to flush file '%s'"), ctx.filename);
1652 if (fsync(fileno(ctx.fh)))
1653 syserr(_("unable to sync file '%s'"), ctx.filename);
1654 if (fclose(ctx.fh))
1655 syserr(_("unable to close file '%s'"), ctx.filename);
1657 /* Put in place atomically. */
1658 xrename(filenew, file);
1660 free(filenew);
1661 free(file);
1664 static const char *
1665 alternative_set_current(struct alternative *a, char *new_choice)
1667 a->known_current = true;
1668 a->current = new_choice;
1670 return new_choice;
1673 static const char *
1674 alternative_get_current(struct alternative *a)
1676 char *curlink;
1677 char *file;
1679 if (a->known_current)
1680 return a->current;
1682 curlink = xasprintf("%s/%s", altdir, a->master_name);
1683 file = fsys_areadlink(curlink);
1684 if (file == NULL && errno != ENOENT)
1685 syserr(_("cannot stat file '%s%s'"), instdir, curlink);
1686 free(curlink);
1688 return alternative_set_current(a, file);
1691 static struct fileset *
1692 alternative_get_best(struct alternative *a)
1694 struct fileset *fs, *best;
1695 const char *current;
1697 current = alternative_get_current(a);
1698 if (current)
1699 best = alternative_get_fileset(a, current);
1700 else
1701 best = NULL;
1703 if (best == NULL)
1704 best = a->choices;
1706 for (fs = a->choices; fs; fs = fs->next)
1707 if (fs->priority > best->priority)
1708 best = fs;
1710 return best;
1713 static void
1714 alternative_display_query(struct alternative *a)
1716 struct fileset *best, *fs;
1717 struct slave_link *sl;
1718 const char *current;
1720 pr("Name: %s", a->master_name);
1721 pr("Link: %s", a->master_link);
1722 if (alternative_slaves_count(a) > 0) {
1723 pr("Slaves:");
1724 for (sl = a->slaves; sl; sl = sl->next)
1725 pr(" %s %s", sl->name, sl->link);
1727 pr("Status: %s", alternative_status_string(a->status));
1728 best = alternative_get_best(a);
1729 if (best)
1730 pr("Best: %s", best->master_file);
1731 current = alternative_get_current(a);
1732 pr("Value: %s", current ? current : "none");
1734 for (fs = a->choices; fs; fs = fs->next) {
1735 printf("\n");
1736 pr("Alternative: %s", fs->master_file);
1737 pr("Priority: %d", fs->priority);
1738 if (alternative_slaves_count(a) == 0)
1739 continue;
1740 pr("Slaves:");
1741 for (sl = a->slaves; sl; sl = sl->next) {
1742 if (fileset_has_slave(fs, sl->name))
1743 pr(" %s %s", sl->name,
1744 fileset_get_slave(fs, sl->name));
1749 static void
1750 alternative_display_user(struct alternative *a)
1752 const char *current;
1753 struct fileset *fs;
1754 struct slave_link *sl;
1756 pr("%s - %s", a->master_name, alternative_status_describe(a->status));
1757 fs = alternative_get_best(a);
1758 if (fs)
1759 pr(_(" link best version is %s"), fs->master_file);
1760 else
1761 pr(_(" link best version not available"));
1762 current = alternative_get_current(a);
1763 if (current) {
1764 pr(_(" link currently points to %s"), current);
1765 } else {
1766 pr(_(" link currently absent"));
1768 pr(_(" link %s is %s"), a->master_name, a->master_link);
1769 for (sl = a->slaves; sl; sl = sl->next)
1770 pr(_(" slave %s is %s"), sl->name, sl->link);
1772 for (fs = a->choices; fs; fs = fs->next) {
1773 pr(_("%s - priority %d"), fs->master_file, fs->priority);
1774 for (sl = a->slaves; sl; sl = sl->next) {
1775 if (fileset_has_slave(fs, sl->name))
1776 pr(_(" slave %s: %s"), sl->name,
1777 fileset_get_slave(fs, sl->name));
1782 static void
1783 alternative_display_list(struct alternative *a)
1785 struct fileset *fs;
1787 for (fs = a->choices; fs; fs = fs->next)
1788 pr("%s", fs->master_file);
1791 static void
1792 alternative_print_choice(struct alternative *a, enum alternative_status status,
1793 struct fileset *fs, int idx, int len)
1795 const char *current = alternative_get_current(a);
1796 int mark;
1798 if (a->status == status &&
1799 current && strcmp(current, fs->master_file) == 0)
1800 mark = '*';
1801 else
1802 mark = ' ';
1804 pr("%c %-12d %-*s % -10d %s", mark, idx, len,
1805 fs->master_file, fs->priority, alternative_status_describe(status));
1808 static char *
1809 alternative_select_choice(struct alternative *a)
1811 const char *current;
1812 char *ret, selection[_POSIX_PATH_MAX];
1813 struct fileset *best, *fs;
1814 int n_choices;
1815 int len, idx;
1817 n_choices = alternative_choices_count(a);
1818 current = alternative_get_current(a);
1819 best = alternative_get_best(a);
1820 assert(best);
1822 len = 15;
1823 for (fs = a->choices; fs; fs = fs->next)
1824 len = max(len, (int)strlen(fs->master_file) + 1);
1826 for (;;) {
1827 pr(P_("There is %d choice for the alternative %s (providing %s).",
1828 "There are %d choices for the alternative %s (providing %s).",
1829 n_choices), n_choices, a->master_name, a->master_link);
1830 printf("\n");
1832 pr(" %-12.12s %-*.*s %-10.10s %s", _("Selection"), len, len,
1833 _("Path"), _("Priority"), _("Status"));
1834 pr("------------------------------------------------------------");
1835 idx = 0;
1836 alternative_print_choice(a, ALT_ST_AUTO, best, idx++, len);
1837 for (fs = a->choices; fs; fs = fs->next, idx++)
1838 alternative_print_choice(a, ALT_ST_MANUAL, fs, idx, len);
1839 printf("\n");
1840 printf(_("Press <enter> to keep the current choice[*], "
1841 "or type selection number: "));
1842 ret = fgets(selection, sizeof(selection), stdin);
1843 if (ret == NULL || strlen(selection) == 0) {
1844 return NULL;
1846 selection[strlen(selection) - 1] = '\0';
1847 if (strlen(selection) == 0)
1848 return xstrdup(current);
1849 errno = 0;
1850 idx = strtol(selection, &ret, 10);
1851 if (idx >= 0 && errno == 0 && *ret == '\0') {
1852 /* Look up by index */
1853 if (idx == 0) {
1854 alternative_set_status(a, ALT_ST_AUTO);
1855 return xstrdup(best->master_file);
1857 idx--;
1858 for (fs = a->choices; idx && fs; idx--)
1859 fs = fs->next;
1860 if (fs) {
1861 alternative_set_status(a, ALT_ST_MANUAL);
1862 return xstrdup(fs->master_file);
1864 } else {
1865 /* Look up by name */
1866 fs = alternative_get_fileset(a, selection);
1867 if (fs) {
1868 alternative_set_status(a, ALT_ST_MANUAL);
1869 return xstrdup(selection);
1875 static char *
1876 alternative_config(struct alternative *a, const char *current_choice)
1878 char *new_choice = NULL;
1880 if (alternative_choices_count(a) == 0) {
1881 pr(_("There is no program which provides %s."),
1882 a->master_name);
1883 pr(_("Nothing to configure."));
1884 } else if (opt_skip_auto && a->status == ALT_ST_AUTO) {
1885 alternative_display_user(a);
1886 } else {
1887 new_choice = alternative_select_choice(a);
1890 return new_choice;
1893 static void
1894 alternative_add_commit_op(struct alternative *a, enum opcode opcode,
1895 const char *arg_a, const char *arg_b)
1897 struct commit_operation *op, *cur;
1899 op = xmalloc(sizeof(*op));
1900 op->opcode = opcode;
1901 op->arg_a = xstrdup(arg_a);
1902 op->arg_b = xstrdup(arg_b);
1903 op->next = NULL;
1905 /* Add at the end */
1906 cur = a->commit_ops;
1907 while (cur && cur->next)
1908 cur = cur->next;
1909 if (cur)
1910 cur->next = op;
1911 else
1912 a->commit_ops = op;
1915 static void
1916 alternative_commit(struct alternative *a)
1918 struct commit_operation *op;
1920 for (op = a->commit_ops; op; op = op->next) {
1921 switch (op->opcode) {
1922 case OPCODE_NOP:
1923 break;
1924 case OPCODE_RM:
1925 fsys_rm(op->arg_a);
1926 break;
1927 case OPCODE_MV:
1928 fsys_mv(op->arg_a, op->arg_b);
1929 break;
1930 case OPCODE_REF_TIME:
1931 fsys_set_ref_time(op->arg_a, op->arg_b);
1932 break;
1936 alternative_commit_operations_free(a);
1939 enum alternative_path_status {
1940 ALT_PATH_SYMLINK,
1941 ALT_PATH_MISSING,
1942 ALT_PATH_OTHER,
1945 static enum alternative_path_status
1946 alternative_path_classify(const char *linkname)
1948 struct stat st;
1950 if (fsys_lstat(linkname, &st) == -1) {
1951 if (errno != ENOENT)
1952 syserr(_("cannot stat file '%s%s'"), instdir, linkname);
1953 return ALT_PATH_MISSING;
1954 } else if (S_ISLNK(st.st_mode)) {
1955 return ALT_PATH_SYMLINK;
1956 } else {
1957 return ALT_PATH_OTHER;
1961 static bool
1962 alternative_path_can_remove(const char *linkname)
1964 if (opt_force)
1965 return true;
1967 if (alternative_path_classify(linkname) == ALT_PATH_OTHER)
1968 return false;
1969 else
1970 return true;
1973 static bool
1974 alternative_path_needs_update(const char *linkname, const char *filename)
1976 char *linktarget;
1977 bool update;
1979 if (opt_force)
1980 return true;
1982 switch (alternative_path_classify(linkname)) {
1983 case ALT_PATH_SYMLINK:
1984 linktarget = fsys_xreadlink(linkname);
1985 if (strcmp(linktarget, filename) == 0)
1986 update = false;
1987 else
1988 update = true;
1989 free(linktarget);
1991 return update;
1992 case ALT_PATH_OTHER:
1993 warning(_("not replacing %s with a link"), linkname);
1994 return false;
1995 case ALT_PATH_MISSING:
1996 default:
1997 return true;
2001 static void
2002 alternative_prepare_install_single(struct alternative *a, const char *name,
2003 const char *linkname, const char *file)
2005 char *fntmp, *fn;
2007 /* Create alternatives directory (/etc/alternatives) if missing. */
2008 if (fsys_pathname_is_missing(altdir)) {
2009 char *root_altdir = fsys_get_path(altdir);
2011 if (make_path(root_altdir, 0755) < 0)
2012 syserr(_("cannot create alternatives directory '%s'"),
2013 root_altdir);
2015 free(root_altdir);
2018 fn = xasprintf("%s/%s", altdir, name);
2020 /* Create link in /etc/alternatives. */
2021 fntmp = xasprintf("%s/%s" ALT_TMP_EXT, altdir, name);
2022 fsys_symlink(file, fntmp);
2023 alternative_add_commit_op(a, OPCODE_MV, fntmp, fn);
2024 if (fsys_pathname_is_missing(fn))
2025 alternative_add_commit_op(a, OPCODE_REF_TIME, fn, file);
2026 free(fntmp);
2028 if (alternative_path_needs_update(linkname, fn)) {
2029 /* Create alternative link. */
2030 fntmp = xasprintf("%s" ALT_TMP_EXT, linkname);
2031 fsys_symlink(fn, fntmp);
2032 alternative_add_commit_op(a, OPCODE_MV, fntmp, linkname);
2033 if (fsys_pathname_is_missing(linkname))
2034 alternative_add_commit_op(a, OPCODE_REF_TIME, linkname, fn);
2035 free(fntmp);
2037 free(fn);
2040 static void
2041 alternative_prepare_install(struct alternative *a, const char *choice)
2043 struct slave_link *sl;
2044 struct fileset *fs;
2046 fs = alternative_get_fileset(a, choice);
2047 if (fs == NULL)
2048 error(_("can't install unknown choice %s"), choice);
2050 /* Take care of master alternative */
2051 alternative_prepare_install_single(a, a->master_name, a->master_link,
2052 choice);
2054 /* Take care of slaves alternatives */
2055 for (sl = a->slaves; sl; sl = sl->next) {
2056 char *fn;
2058 if (fileset_can_install_slave(fs, sl->name)) {
2059 alternative_prepare_install_single(a, sl->name,
2060 sl->link, fileset_get_slave(fs, sl->name));
2061 continue;
2064 /* Slave can't be installed */
2065 if (fileset_has_slave(fs, sl->name))
2066 warning(_("skip creation of %s because associated "
2067 "file %s (of link group %s) doesn't exist"),
2068 sl->link, fileset_get_slave(fs, sl->name),
2069 a->master_name);
2071 /* Drop unused slave. */
2072 fn = xasprintf("%s/%s", altdir, sl->name);
2073 if (alternative_path_can_remove(sl->link))
2074 alternative_add_commit_op(a, OPCODE_RM, sl->link, NULL);
2075 else
2076 warning(_("not removing %s since it's not a symlink"),
2077 sl->link);
2078 alternative_add_commit_op(a, OPCODE_RM, fn, NULL);
2079 free(fn);
2083 static void
2084 alternative_remove_files(struct alternative *a)
2086 struct slave_link *sl;
2088 fsys_rm_args("%s" ALT_TMP_EXT, a->master_link);
2089 if (alternative_path_can_remove(a->master_link))
2090 fsys_rm(a->master_link);
2092 fsys_rm_args("%s/%s" ALT_TMP_EXT, altdir, a->master_name);
2093 fsys_rm_args("%s/%s", altdir, a->master_name);
2095 for (sl = a->slaves; sl; sl = sl->next) {
2096 fsys_rm_args("%s" ALT_TMP_EXT, sl->link);
2097 if (alternative_path_can_remove(sl->link))
2098 fsys_rm(sl->link);
2100 fsys_rm_args("%s/%s" ALT_TMP_EXT, altdir, sl->name);
2101 fsys_rm_args("%s/%s", altdir, sl->name);
2103 /* Drop admin file */
2104 xunlink_args("%s/%s", admdir, a->master_name);
2107 static char *
2108 alternative_remove(struct alternative *a, const char *current_choice,
2109 const char *path)
2111 char *new_choice = NULL;
2113 if (alternative_has_choice(a, path))
2114 alternative_remove_choice(a, path);
2115 else
2116 verbose(_("alternative %s for %s not registered; not removing"),
2117 path, a->master_name);
2119 if (current_choice && strcmp(current_choice, path) == 0) {
2120 struct fileset *best;
2122 /* Current choice is removed. */
2123 if (a->status == ALT_ST_MANUAL) {
2124 /* And it was manual, switch to auto. */
2125 info(_("removing manually selected alternative "
2126 "- switching %s to auto mode"),
2127 a->master_name);
2128 alternative_set_status(a, ALT_ST_AUTO);
2130 best = alternative_get_best(a);
2131 if (best)
2132 new_choice = xstrdup(best->master_file);
2135 return new_choice;
2138 static bool
2139 alternative_has_broken_symlink(const char *linkname, const char *ref_target)
2141 char *target;
2143 target = fsys_areadlink(linkname);
2144 if (!target)
2145 return true;
2146 if (strcmp(target, ref_target) != 0) {
2147 free(target);
2148 return true;
2150 free(target);
2151 return false;
2154 static bool
2155 alternative_has_broken_slave(struct slave_link *sl, struct fileset *fs)
2157 if (fileset_can_install_slave(fs, sl->name)) {
2158 char *wanted;
2159 const char *sl_target;
2161 /* Verify link -> /etc/alternatives/foo */
2162 wanted = xasprintf("%s/%s", altdir, sl->name);
2163 if (alternative_has_broken_symlink(sl->link, wanted)) {
2164 free(wanted);
2165 return true;
2168 /* Verify /etc/alternatives/foo -> file */
2169 sl_target = fileset_get_slave(fs, sl->name);
2170 if (alternative_has_broken_symlink(wanted, sl_target)) {
2171 free(wanted);
2172 return true;
2175 free(wanted);
2176 } else {
2177 char *sl_altlnk;
2179 /* Slave link must not exist. */
2180 if (alternative_path_classify(sl->link) != ALT_PATH_MISSING)
2181 return true;
2182 sl_altlnk = xasprintf("%s/%s", altdir, sl->name);
2183 if (alternative_path_classify(sl_altlnk) != ALT_PATH_MISSING) {
2184 free(sl_altlnk);
2185 return true;
2187 free(sl_altlnk);
2190 return false;
2193 static enum alternative_update_reason
2194 alternative_needs_update(struct alternative *a)
2196 enum alternative_update_reason reason = ALT_UPDATE_NO;
2197 const char *current;
2198 char *wanted;
2199 struct fileset *fs;
2200 struct slave_link *sl;
2202 /* Check master link */
2203 wanted = xasprintf("%s/%s", altdir, a->master_name);
2204 if (alternative_has_broken_symlink(a->master_link, wanted)) {
2205 free(wanted);
2206 return ALT_UPDATE_LINK_BROKEN;
2208 free(wanted);
2210 /* Stop if we have an unmanaged alternative */
2211 current = alternative_get_current(a);
2212 if (current == NULL)
2213 return ALT_UPDATE_LINK_BROKEN;
2215 fs = alternative_get_fileset(a, current);
2217 /* Stop if we do not have the choice. */
2218 if (fs == NULL)
2219 return ALT_UPDATE_NO;
2221 /* Check slaves */
2222 for (sl = a->slaves; sl; sl = sl->next) {
2223 if (alternative_has_broken_slave(sl, fs)) {
2224 if (sl->updated)
2225 reason = ALT_UPDATE_SLAVE_CHANGED;
2226 else
2227 return ALT_UPDATE_LINK_BROKEN;
2231 return reason;
2234 struct alternative_map {
2235 struct alternative_map *next;
2237 const char *key;
2238 struct alternative *item;
2241 static struct alternative_map *
2242 alternative_map_new(const char *key, struct alternative *a)
2244 struct alternative_map *am;
2246 am = xmalloc(sizeof(*am));
2247 am->next = NULL;
2248 am->key = key;
2249 am->item = a;
2251 return am;
2254 static struct alternative *
2255 alternative_map_find(struct alternative_map *am, const char *key)
2257 for (; am; am = am->next)
2258 if (am->key && strcmp(am->key, key) == 0)
2259 return am->item;
2261 return NULL;
2264 static void
2265 alternative_map_add(struct alternative_map *am, const char *key,
2266 struct alternative *a)
2268 alternative_ref(a);
2270 if (am->key == NULL) {
2271 am->key = key;
2272 am->item = a;
2273 } else {
2274 struct alternative_map *new = alternative_map_new(key, a);
2276 while (am->next)
2277 am = am->next;
2278 am->next = new;
2282 static void
2283 alternative_map_load_names(struct alternative_map *alt_map_obj)
2285 struct dirent **table;
2286 int i, count;
2288 count = altdb_get_namelist(&table);
2289 for (i = 0; i < count; i++) {
2290 struct alternative *a_new = alternative_new(table[i]->d_name);
2292 if (!alternative_load(a_new, ALTDB_LAX_PARSER)) {
2293 alternative_free(a_new);
2294 continue;
2296 alternative_map_add(alt_map_obj, a_new->master_name, a_new);
2298 alternative_unref(a_new);
2300 altdb_free_namelist(table, count);
2303 static void
2304 alternative_map_load_tree(struct alternative_map *alt_map_links,
2305 struct alternative_map *alt_map_parent)
2307 struct dirent **table;
2308 int i, count;
2310 count = altdb_get_namelist(&table);
2311 for (i = 0; i < count; i++) {
2312 struct slave_link *sl;
2313 struct alternative *a_new = alternative_new(table[i]->d_name);
2315 if (!alternative_load(a_new, ALTDB_LAX_PARSER)) {
2316 alternative_free(a_new);
2317 continue;
2319 alternative_map_add(alt_map_links, a_new->master_link, a_new);
2320 alternative_map_add(alt_map_parent, a_new->master_name, a_new);
2321 for (sl = a_new->slaves; sl; sl = sl->next) {
2322 alternative_map_add(alt_map_links, sl->link, a_new);
2323 alternative_map_add(alt_map_parent, sl->name, a_new);
2326 alternative_unref(a_new);
2328 altdb_free_namelist(table, count);
2331 static void
2332 alternative_map_free(struct alternative_map *am)
2334 struct alternative_map *am_next;
2336 while (am) {
2337 am_next = am->next;
2338 if (am->item)
2339 alternative_free(am->item);
2340 free(am);
2341 am = am_next;
2345 static char *
2346 alternative_set_manual(struct alternative *a, const char *path)
2348 char *new_choice = NULL;
2350 if (alternative_has_choice(a, path))
2351 new_choice = xstrdup(path);
2352 else
2353 error(_("alternative %s for %s not registered; "
2354 "not setting"), path, a->master_name);
2355 alternative_set_status(a, ALT_ST_MANUAL);
2357 return new_choice;
2360 static char *
2361 alternative_set_auto(struct alternative *a)
2363 char *new_choice = NULL;
2365 alternative_set_status(a, ALT_ST_AUTO);
2366 if (alternative_choices_count(a) == 0)
2367 info(_("there is no program which provides %s"),
2368 a->master_name);
2369 else
2370 new_choice = xstrdup(alternative_get_best(a)->master_file);
2372 return new_choice;
2375 static const char *
2376 get_argv_string(int argc, char **argv)
2378 static char string[2048];
2379 size_t cur_len;
2380 int i;
2382 string[0] = '\0';
2383 cur_len = 0;
2384 for (i = 1; i < argc; i++) {
2385 size_t arg_len = strlen(argv[i]);
2387 if (cur_len + arg_len + 2 > sizeof(string))
2388 break;
2389 if (cur_len) {
2390 strcpy(string + cur_len, " ");
2391 cur_len++;
2393 strcpy(string + cur_len, argv[i]);
2394 cur_len += arg_len;
2397 return string;
2400 static void
2401 alternative_select_mode(struct alternative *a, const char *current_choice)
2403 if (current_choice) {
2404 /* Detect manually modified alternative, switch to manual. */
2405 if (!alternative_has_choice(a, current_choice)) {
2406 if (fsys_pathname_is_missing(current_choice)) {
2407 warning(_("%s%s/%s is dangling; it will be updated "
2408 "with best choice"), instdir, altdir,
2409 a->master_name);
2410 alternative_set_status(a, ALT_ST_AUTO);
2411 } else if (a->status != ALT_ST_MANUAL) {
2412 warning(_("%s%s/%s has been changed (manually or by "
2413 "a script); switching to manual "
2414 "updates only"), instdir, altdir,
2415 a->master_name);
2416 alternative_set_status(a, ALT_ST_MANUAL);
2419 } else {
2420 /* Lack of alternative link => automatic mode. */
2421 verbose(_("setting up automatic selection of %s"),
2422 a->master_name);
2423 alternative_set_status(a, ALT_ST_AUTO);
2427 static void
2428 alternative_evolve_slave(struct alternative *a, const char *cur_choice,
2429 struct slave_link *sl, struct fileset *fs)
2431 struct slave_link *sl_old;
2432 char *new_file = NULL;
2433 const char *old, *new;
2435 sl_old = alternative_get_slave(a, sl->name);
2436 if (sl_old == NULL) {
2437 sl->updated = true;
2438 return;
2441 old = sl_old->link;
2442 new = sl->link;
2444 if (cur_choice && strcmp(cur_choice, fs->master_file) == 0) {
2445 new_file = xstrdup(fileset_get_slave(fs, sl->name));
2446 } else {
2447 char *lnk;
2449 lnk = xasprintf("%s/%s", altdir, sl->name);
2450 new_file = fsys_areadlink(lnk);
2451 free(lnk);
2453 if (strcmp(old, new) != 0 &&
2454 alternative_path_classify(old) == ALT_PATH_SYMLINK) {
2455 bool rename_link = false;
2457 if (new_file)
2458 rename_link = !fsys_pathname_is_missing(new_file);
2460 if (rename_link) {
2461 info(_("renaming %s slave link from %s%s to %s%s"),
2462 sl->name, instdir, old, instdir, new);
2463 fsys_mv(old, new);
2464 } else {
2465 fsys_rm(old);
2468 sl->updated = true;
2470 free(new_file);
2473 static void
2474 alternative_evolve(struct alternative *a, struct alternative *b,
2475 const char *cur_choice, struct fileset *fs)
2477 struct slave_link *sl;
2478 bool is_link;
2480 is_link = alternative_path_classify(a->master_link) == ALT_PATH_SYMLINK;
2481 if (is_link && strcmp(a->master_link, b->master_link) != 0) {
2482 info(_("renaming %s link from %s%s to %s%s"), b->master_name,
2483 instdir, a->master_link, instdir, b->master_link);
2484 fsys_mv(a->master_link, b->master_link);
2486 alternative_set_link(a, b->master_link);
2488 /* Check if new slaves have been added, or existing
2489 * ones renamed. */
2490 for (sl = b->slaves; sl; sl = sl->next) {
2491 alternative_evolve_slave(a, cur_choice, sl, fs);
2492 alternative_copy_slave(a, sl);
2496 static char *
2497 alternative_install(struct alternative **aptr, struct alternative *inst_alt,
2498 const char *current_choice, struct fileset *fileset)
2500 struct alternative *a = *aptr;
2501 char *new_choice = NULL;
2503 if (a->master_link) {
2504 /* Alternative already exists, check if anything got
2505 * updated. */
2506 alternative_evolve(a, inst_alt, current_choice, fileset);
2507 alternative_free(inst_alt);
2508 } else {
2509 /* Alternative doesn't exist, create from parameters. */
2510 alternative_free(a);
2511 *aptr = a = inst_alt;
2513 alternative_add_choice(a, fileset);
2514 if (a->status == ALT_ST_AUTO) {
2515 new_choice = xstrdup(alternative_get_best(a)->master_file);
2516 } else {
2517 verbose(_("automatic updates of %s/%s are disabled; "
2518 "leaving it alone"), altdir, a->master_name);
2519 verbose(_("to return to automatic updates use "
2520 "'%s --auto %s'"), PROGNAME, a->master_name);
2522 return new_choice;
2525 static void
2526 alternative_update(struct alternative *a,
2527 const char *current_choice, const char *new_choice)
2529 enum alternative_update_reason reason;
2531 /* No choice left, remove everything. */
2532 if (!alternative_choices_count(a)) {
2533 log_msg("link group %s fully removed", a->master_name);
2534 alternative_remove_files(a);
2535 return;
2538 /* New choice wanted. */
2539 if (new_choice &&
2540 (!current_choice || strcmp(new_choice, current_choice) != 0)) {
2541 log_msg("link group %s updated to point to %s", a->master_name,
2542 new_choice);
2543 if (a->status == ALT_ST_AUTO)
2544 info(_("using %s to provide %s (%s) in auto mode"),
2545 new_choice, a->master_link, a->master_name);
2546 else
2547 info(_("using %s to provide %s (%s) in manual mode"),
2548 new_choice, a->master_link, a->master_name);
2549 debug("prepare_install(%s)", new_choice);
2550 alternative_prepare_install(a, new_choice);
2551 } else if ((reason = alternative_needs_update(a))) {
2552 if (reason == ALT_UPDATE_SLAVE_CHANGED) {
2553 log_msg("link group %s updated with changed slaves",
2554 a->master_name);
2555 info(_("updating alternative %s "
2556 "because link group %s has changed slave links"),
2557 current_choice, a->master_name);
2558 } else {
2559 log_msg("auto-repair link group %s", a->master_name);
2560 warning(_("forcing reinstallation of alternative %s "
2561 "because link group %s is broken"),
2562 current_choice, a->master_name);
2565 if (current_choice && !alternative_has_choice(a, current_choice)) {
2566 struct fileset *best = alternative_get_best(a);
2568 warning(_("current alternative %s is unknown, "
2569 "switching to %s for link group %s"),
2570 current_choice, best->master_file,
2571 a->master_name);
2572 current_choice = best->master_file;
2573 alternative_set_status(a, ALT_ST_AUTO);
2576 if (current_choice)
2577 alternative_prepare_install(a, current_choice);
2580 /* Save administrative file if needed. */
2581 if (a->modified) {
2582 debug("%s is modified and will be saved", a->master_name);
2583 alternative_save(a);
2586 /* Replace all symlinks in one pass. */
2587 alternative_commit(a);
2590 static void
2591 alternative_config_all(void)
2593 struct alternative_map *alt_map_obj;
2594 struct alternative_map *am;
2596 alt_map_obj = alternative_map_new(NULL, NULL);
2597 alternative_map_load_names(alt_map_obj);
2599 for (am = alt_map_obj; am && am->item; am = am->next) {
2600 const char *current_choice;
2601 char *new_choice;
2603 current_choice = alternative_get_current(am->item);
2604 alternative_select_mode(am->item, current_choice);
2606 new_choice = alternative_config(am->item, current_choice);
2608 alternative_update(am->item, current_choice, new_choice);
2610 free(new_choice);
2613 alternative_map_free(alt_map_obj);
2616 static void
2617 alternative_get_selections(void)
2619 struct alternative_map *alt_map_obj;
2620 struct alternative_map *am;
2622 alt_map_obj = alternative_map_new(NULL, NULL);
2623 alternative_map_load_names(alt_map_obj);
2625 for (am = alt_map_obj; am && am->item; am = am->next) {
2626 const char *current;
2628 current = alternative_get_current(am->item);
2629 printf("%-30s %-8s %s\n", am->key,
2630 alternative_status_string(am->item->status),
2631 current ? current : "");
2634 alternative_map_free(alt_map_obj);
2637 static void
2638 alternative_set_selection(struct alternative_map *all, const char *name,
2639 const char *status, const char *choice)
2641 struct alternative *a;
2643 debug("set_selection(%s, %s, %s)", name, status, choice);
2644 a = alternative_map_find(all, name);
2645 if (a) {
2646 char *new_choice = NULL;
2648 if (strcmp(status, "auto") == 0) {
2649 info(_("selecting alternative %s as auto"), name);
2650 new_choice = alternative_set_auto(a);
2651 } else if (alternative_has_choice(a, choice)) {
2652 info(_("selecting alternative %s as choice %s"), name,
2653 choice);
2654 new_choice = alternative_set_manual(a, choice);
2655 } else {
2656 info(_("alternative %s unchanged because choice "
2657 "%s is not available"), name, choice);
2660 if (new_choice) {
2661 const char *current_choice;
2663 current_choice = alternative_get_current(a);
2664 alternative_select_mode(a, current_choice);
2666 alternative_update(a, current_choice, new_choice);
2668 free(new_choice);
2670 } else {
2671 info(_("skip unknown alternative %s"), name);
2675 static void
2676 alternative_set_selections(FILE *input, const char *desc)
2678 struct alternative_map *alt_map_obj;
2680 alt_map_obj = alternative_map_new(NULL, NULL);
2681 alternative_map_load_names(alt_map_obj);
2683 for (;;) {
2684 char line[1024], *res, *name, *status, *choice;
2685 size_t len, i;
2687 errno = 0;
2688 /* Can't use scanf("%s %s %s") because choice can
2689 * contain a space */
2690 res = fgets(line, sizeof(line), input);
2691 if (res == NULL && errno) {
2692 syserr(_("read error in %.250s"), desc);
2693 } else if (res == NULL) {
2694 break;
2696 len = strlen(line);
2697 if (len == 0 || line[len - 1] != '\n') {
2698 error(_("line too long or not terminated while "
2699 "trying to read %s"), desc);
2701 line[len - 1] = '\0';
2702 len--;
2704 /* Delimit name string in line */
2705 i = 0;
2706 name = line;
2707 while (i < len && !isblank(line[i]))
2708 i++;
2709 if (i >= len) {
2710 info(_("skip invalid selection line: %s"), line);
2711 continue;
2713 line[i++] = '\0';
2714 while (i < len && isblank(line[i]))
2715 i++;
2717 /* Delimit status string in line */
2718 status = line + i;
2719 while (i < len && !isblank(line[i]))
2720 i++;
2721 if (i >= len) {
2722 info(_("skip invalid selection line: %s"), line);
2723 continue;
2725 line[i++] = '\0';
2726 while (i < len && isblank(line[i]))
2727 i++;
2729 /* Delimit choice string in the line */
2730 if (i >= len) {
2731 info(_("skip invalid selection line: %s"), line);
2732 continue;
2734 choice = line + i;
2736 alternative_set_selection(alt_map_obj, name, status, choice);
2739 alternative_map_free(alt_map_obj);
2742 static void
2743 alternative_check_name(const char *name)
2745 if (strpbrk(name, "/ \t"))
2746 error(_("alternative name (%s) must not contain '/' "
2747 "and spaces"), name);
2750 static void
2751 alternative_check_link(const char *linkname)
2753 if (linkname[0] != '/')
2754 error(_("alternative link is not absolute as it should be: %s"),
2755 linkname);
2758 static void
2759 alternative_check_path(const char *file)
2761 if (!file || file[0] != '/')
2762 error(_("alternative path is not absolute as it should be: %s"),
2763 file);
2767 * Check the alternative installation arguments.
2769 * That the caller doesn't mix links between alternatives, doesn't mix
2770 * alternatives between slave/master, and that the various parameters
2771 * are fine.
2773 static void
2774 alternative_check_install_args(struct alternative *inst_alt,
2775 struct fileset *fileset)
2777 struct alternative_map *alt_map_links, *alt_map_parent;
2778 struct alternative *found;
2779 struct slave_link *sl;
2781 alternative_check_name(inst_alt->master_name);
2782 alternative_check_link(inst_alt->master_link);
2783 alternative_check_path(fileset->master_file);
2785 /* Load information about all alternatives to check for mistakes. */
2786 alt_map_links = alternative_map_new(NULL, NULL);
2787 alt_map_parent = alternative_map_new(NULL, NULL);
2788 alternative_map_load_tree(alt_map_links, alt_map_parent);
2790 found = alternative_map_find(alt_map_parent, inst_alt->master_name);
2791 if (found && strcmp(found->master_name, inst_alt->master_name) != 0) {
2792 error(_("alternative %s can't be master: it is a slave of %s"),
2793 inst_alt->master_name, found->master_name);
2796 found = alternative_map_find(alt_map_links, inst_alt->master_link);
2797 if (found && strcmp(found->master_name, inst_alt->master_name) != 0) {
2798 found = alternative_map_find(alt_map_parent,
2799 found->master_name);
2800 error(_("alternative link %s is already managed by %s"),
2801 inst_alt->master_link, found->master_name);
2804 if (fsys_pathname_is_missing(fileset->master_file))
2805 error(_("alternative path %s%s doesn't exist"),
2806 instdir, fileset->master_file);
2808 for (sl = inst_alt->slaves; sl; sl = sl->next) {
2809 const char *file = fileset_get_slave(fileset, sl->name);
2811 alternative_check_name(sl->name);
2812 alternative_check_link(sl->link);
2813 alternative_check_path(file);
2815 found = alternative_map_find(alt_map_parent, sl->name);
2816 if (found &&
2817 strcmp(found->master_name, inst_alt->master_name) != 0) {
2818 if (strcmp(found->master_name, sl->name) == 0)
2819 error(_("alternative %s can't be slave of %s: "
2820 "it is a master alternative"),
2821 sl->name, inst_alt->master_name);
2822 else
2823 error(_("alternative %s can't be slave of %s: "
2824 "it is a slave of %s"),
2825 sl->name, inst_alt->master_name,
2826 found->master_name);
2829 found = alternative_map_find(alt_map_links, sl->link);
2830 if (found &&
2831 strcmp(found->master_name, inst_alt->master_name) != 0) {
2832 error(_("alternative link %s is already "
2833 "managed by %s"), sl->link,
2834 found->master_name);
2836 if (found) {
2837 struct slave_link *sl2;
2839 for (sl2 = found->slaves; sl2; sl2 = sl2->next)
2840 if (strcmp(sl2->link, sl->link) == 0)
2841 break;
2842 if (sl2 && strcmp(sl2->name, sl->name) != 0)
2843 error(_("alternative link %s is already "
2844 "managed by %s (slave of %s)"),
2845 sl->link, sl2->name,
2846 found->master_name);
2850 alternative_map_free(alt_map_links);
2851 alternative_map_free(alt_map_parent);
2855 * Main program
2858 static void
2859 set_action(enum action new_action)
2861 if (action)
2862 badusage(_("two commands specified: --%s and --%s"),
2863 action_names[action].name, action_names[new_action].name);
2864 action = new_action;
2867 static void
2868 set_action_from_name(const char *new_action)
2870 size_t i;
2872 for (i = 0; i < array_count(action_names); i++) {
2873 if (strcmp(new_action, action_names[i].name) == 0) {
2874 set_action(action_names[i].action);
2875 return;
2879 assert(!"unknown action name");
2882 static const char *
2883 set_rootdir(const char *dir)
2885 instdir = fsys_set_dir(dir);
2886 free(log_file);
2887 log_file = fsys_get_path(LOGDIR "/alternatives.log");
2888 altdir = SYSCONFDIR "/alternatives";
2889 free(admdir);
2890 admdir = fsys_gen_admindir();
2892 return instdir;
2895 static char *
2896 admindir_init(void)
2898 const char *basedir_env;
2900 /* Try to get the admindir from an environment variable, usually set
2901 * by the system package manager. */
2902 basedir_env = getenv(ADMINDIR_ENVVAR);
2903 if (basedir_env)
2904 return xasprintf("%s%s", basedir_env, "/alternatives");
2905 else
2906 return fsys_gen_admindir();
2909 #define MISSING_ARGS(nb) (argc < i + nb + 1)
2912 main(int argc, char **argv)
2914 /* Alternative worked on. */
2915 struct alternative *a = NULL;
2916 /* Alternative to install. */
2917 struct alternative *inst_alt = NULL;
2918 /* Set of files to install in the alternative. */
2919 struct fileset *fileset = NULL;
2920 /* Path of alternative we are offering. */
2921 const char *path = NULL;
2922 const char *current_choice = NULL;
2923 char *new_choice = NULL;
2924 bool modifies_alt = false;
2925 bool modifies_sys = false;
2926 int i = 0;
2928 setlocale(LC_ALL, "");
2929 bindtextdomain(PACKAGE, LOCALEDIR);
2930 textdomain(PACKAGE);
2932 tzset();
2933 umask(022);
2935 instdir = fsys_set_dir(NULL);
2936 admdir = admindir_init();
2937 log_file = fsys_get_path(LOGDIR "/alternatives.log");
2939 if (setvbuf(stdout, NULL, _IONBF, 0))
2940 syserr("setvbuf failed");
2942 prog_path = argv[0];
2944 for (i = 1; i < argc; i++) {
2945 if (strstr(argv[i], "--") != argv[i]) {
2946 error(_("unknown argument '%s'"), argv[i]);
2947 } else if (strcmp("--help", argv[i]) == 0) {
2948 usage();
2949 exit(0);
2950 } else if (strcmp("--version", argv[i]) == 0) {
2951 version();
2952 exit(0);
2953 } else if (strcmp("--quiet", argv[i]) == 0) {
2954 opt_verbose = OUTPUT_QUIET;
2955 } else if (strcmp("--verbose", argv[i]) == 0) {
2956 opt_verbose = OUTPUT_VERBOSE;
2957 } else if (strcmp("--debug", argv[i]) == 0) {
2958 opt_verbose = OUTPUT_DEBUG;
2959 } else if (strcmp("--install", argv[i]) == 0) {
2960 const char *alink, *aname, *apath;
2961 char *prio_str, *prio_end;
2962 long prio;
2964 set_action(ACTION_INSTALL);
2965 if (MISSING_ARGS(4))
2966 badusage(_("--%s needs <link> <name> <path> "
2967 "<priority>"), argv[i] + 2);
2969 alink = argv[i + 1];
2970 aname = argv[i + 2];
2971 apath = argv[i + 3];
2972 prio_str = argv[i + 4];
2974 if (strcmp(alink, apath) == 0)
2975 badusage(_("<link> '%s' is the same as <path>"),
2976 alink);
2977 errno = 0;
2978 prio = strtol(prio_str, &prio_end, 10);
2979 if (prio_str == prio_end || *prio_end != '\0')
2980 badusage(_("priority '%s' must be an integer"),
2981 prio_str);
2982 if (prio < INT_MIN || prio > INT_MAX || errno == ERANGE)
2983 badusage(_("priority '%s' is out of range"),
2984 prio_str);
2986 a = alternative_new(aname);
2987 inst_alt = alternative_new(aname);
2988 alternative_set_status(inst_alt, ALT_ST_AUTO);
2989 alternative_set_link(inst_alt, alink);
2990 fileset = fileset_new(apath, prio);
2992 i += 4;
2993 } else if (strcmp("--remove", argv[i]) == 0 ||
2994 strcmp("--set", argv[i]) == 0) {
2995 set_action_from_name(argv[i] + 2);
2996 if (MISSING_ARGS(2))
2997 badusage(_("--%s needs <name> <path>"), argv[i] + 2);
2999 a = alternative_new(argv[i + 1]);
3000 path = argv[i + 2];
3002 alternative_check_name(a->master_name);
3003 alternative_check_path(path);
3005 i += 2;
3006 } else if (strcmp("--display", argv[i]) == 0 ||
3007 strcmp("--query", argv[i]) == 0 ||
3008 strcmp("--auto", argv[i]) == 0 ||
3009 strcmp("--config", argv[i]) == 0 ||
3010 strcmp("--list", argv[i]) == 0 ||
3011 strcmp("--remove-all", argv[i]) == 0) {
3012 set_action_from_name(argv[i] + 2);
3013 if (MISSING_ARGS(1))
3014 badusage(_("--%s needs <name>"), argv[i] + 2);
3015 a = alternative_new(argv[i + 1]);
3017 alternative_check_name(a->master_name);
3019 i++;
3020 } else if (strcmp("--all", argv[i]) == 0 ||
3021 strcmp("--get-selections", argv[i]) == 0 ||
3022 strcmp("--set-selections", argv[i]) == 0) {
3023 set_action_from_name(argv[i] + 2);
3024 } else if (strcmp("--slave", argv[i]) == 0) {
3025 const char *slink, *sname, *spath;
3026 struct slave_link *sl;
3028 if (action != ACTION_INSTALL)
3029 badusage(_("--%s only allowed with --%s"),
3030 argv[i] + 2, "install");
3031 if (MISSING_ARGS(3))
3032 badusage(_("--%s needs <link> <name> <path>"),
3033 argv[i] + 2);
3035 slink = argv[i + 1];
3036 sname = argv[i + 2];
3037 spath = argv[i + 3];
3039 if (strcmp(slink, spath) == 0)
3040 badusage(_("<link> '%s' is the same as <path>"),
3041 slink);
3042 if (strcmp(inst_alt->master_name, sname) == 0)
3043 badusage(_("<name> '%s' is both primary and slave"),
3044 sname);
3045 if (strcmp(slink, inst_alt->master_link) == 0)
3046 badusage(_("<link> '%s' is both primary and slave"),
3047 slink);
3048 if (alternative_has_slave(inst_alt, sname))
3049 badusage(_("duplicate slave <name> '%s'"), sname);
3051 for (sl = inst_alt->slaves; sl; sl = sl->next) {
3052 const char *linkname = sl->link;
3053 if (linkname == NULL)
3054 linkname = "";
3055 if (strcmp(linkname, slink) == 0)
3056 badusage(_("duplicate slave <link> '%s'"),
3057 slink);
3060 alternative_add_slave(inst_alt, sname, slink);
3061 fileset_add_slave(fileset, sname, spath);
3063 i+= 3;
3064 } else if (strcmp("--log", argv[i]) == 0) {
3065 if (MISSING_ARGS(1))
3066 badusage(_("--%s needs a <file> argument"),
3067 argv[i] + 2);
3068 free(log_file);
3069 log_file = fsys_get_path(argv[i + 1]);
3070 i++;
3071 } else if (strcmp("--altdir", argv[i]) == 0) {
3072 if (MISSING_ARGS(1))
3073 badusage(_("--%s needs a <directory> argument"),
3074 argv[i] + 2);
3075 altdir = argv[i + 1];
3076 i++;
3078 /* If altdir is below instdir, convert it to a relative
3079 * path, as we will prepend instdir as needed. */
3080 if (strncmp(altdir, instdir, instdir_len) == 0)
3081 altdir += instdir_len;
3082 } else if (strcmp("--admindir", argv[i]) == 0) {
3083 if (MISSING_ARGS(1))
3084 badusage(_("--%s needs a <directory> argument"),
3085 argv[i] + 2);
3086 free(admdir);
3087 admdir = xstrdup(argv[i + 1]);
3088 i++;
3089 } else if (strcmp("--instdir", argv[i]) == 0) {
3090 if (MISSING_ARGS(1))
3091 badusage(_("--%s needs a <directory> argument"),
3092 argv[i] + 2);
3093 fsys_set_dir(argv[i + 1]);
3094 i++;
3096 /* If altdir is below instdir, convert it to a relative
3097 * path, as we will prepend instdir as needed. */
3098 if (strncmp(altdir, instdir, instdir_len) == 0)
3099 altdir += instdir_len;
3100 } else if (strcmp("--root", argv[i]) == 0) {
3101 if (MISSING_ARGS(1))
3102 badusage(_("--%s needs a <directory> argument"),
3103 argv[i] + 2);
3104 set_rootdir(argv[i + 1]);
3105 i++;
3106 } else if (strcmp("--skip-auto", argv[i]) == 0) {
3107 opt_skip_auto = 1;
3108 } else if (strcmp("--force", argv[i]) == 0) {
3109 opt_force = 1;
3110 } else {
3111 badusage(_("unknown option '%s'"), argv[i]);
3115 if (action == ACTION_NONE)
3116 badusage(_("need --%s, --%s, --%s, --%s, --%s, --%s, --%s, "
3117 "--%s, --%s, --%s, --%s or --%s"),
3118 "display", "query", "list", "get-selections",
3119 "config", "set", "set-selections", "install",
3120 "remove", "all", "remove-all", "auto");
3122 debug("root=%s admdir=%s altdir=%s", instdir, admdir, altdir);
3124 /* The following actions might modify the current alternative. */
3125 if (action == ACTION_SET ||
3126 action == ACTION_AUTO ||
3127 action == ACTION_CONFIG ||
3128 action == ACTION_REMOVE ||
3129 action == ACTION_REMOVE_ALL ||
3130 action == ACTION_INSTALL)
3131 modifies_alt = true;
3133 /* The following actions might modify the system somehow. */
3134 if (modifies_alt ||
3135 action == ACTION_CONFIG_ALL ||
3136 action == ACTION_SET_SELECTIONS)
3137 modifies_sys = true;
3139 if (action == ACTION_INSTALL)
3140 alternative_check_install_args(inst_alt, fileset);
3142 if (action == ACTION_DISPLAY ||
3143 action == ACTION_QUERY ||
3144 action == ACTION_LIST ||
3145 action == ACTION_SET ||
3146 action == ACTION_AUTO ||
3147 action == ACTION_CONFIG ||
3148 action == ACTION_REMOVE_ALL) {
3149 /* Load the alternative info, stop on failure. */
3150 if (!alternative_load(a, ALTDB_WARN_PARSER))
3151 error(_("no alternatives for %s"), a->master_name);
3152 } else if (action == ACTION_REMOVE) {
3153 /* XXX: Be consistent for now with the case when we
3154 * try to remove a non-existing path from an existing
3155 * link group file. */
3156 if (!alternative_load(a, ALTDB_WARN_PARSER)) {
3157 verbose(_("no alternatives for %s"), a->master_name);
3158 alternative_free(a);
3159 free(log_file);
3160 free(admdir);
3161 exit(0);
3163 } else if (action == ACTION_INSTALL) {
3164 /* Load the alternative info, ignore failures. */
3165 alternative_load(a, ALTDB_WARN_PARSER);
3168 if (modifies_sys)
3169 log_msg("run with %s", get_argv_string(argc, argv));
3171 if (modifies_alt) {
3172 current_choice = alternative_get_current(a);
3173 alternative_select_mode(a, current_choice);
3176 /* Handle actions. */
3177 if (action == ACTION_CONFIG_ALL) {
3178 alternative_config_all();
3179 } else if (action == ACTION_GET_SELECTIONS) {
3180 alternative_get_selections();
3181 } else if (action == ACTION_SET_SELECTIONS) {
3182 alternative_set_selections(stdin, _("<standard input>"));
3183 } else if (action == ACTION_DISPLAY) {
3184 alternative_display_user(a);
3185 } else if (action == ACTION_QUERY) {
3186 alternative_display_query(a);
3187 } else if (action == ACTION_LIST) {
3188 alternative_display_list(a);
3189 } else if (action == ACTION_SET) {
3190 new_choice = alternative_set_manual(a, path);
3191 } else if (action == ACTION_AUTO) {
3192 new_choice = alternative_set_auto(a);
3193 } else if (action == ACTION_CONFIG) {
3194 new_choice = alternative_config(a, current_choice);
3195 } else if (action == ACTION_REMOVE) {
3196 new_choice = alternative_remove(a, current_choice, path);
3197 } else if (action == ACTION_REMOVE_ALL) {
3198 alternative_choices_free(a);
3199 } else if (action == ACTION_INSTALL) {
3200 new_choice = alternative_install(&a, inst_alt, current_choice,
3201 fileset);
3204 if (modifies_alt)
3205 alternative_update(a, current_choice, new_choice);
3207 if (a)
3208 alternative_free(a);
3209 free(new_choice);
3210 free(log_file);
3211 free(admdir);
3213 return 0;