po: Update German man pages translation
[dpkg.git] / utils / update-alternatives.c
blob9909cde68c93dfff2813cacce2b032a69803c5db
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(_("%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 < 0) {
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 < 0)
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)) < 0 && 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 if (fh_log == NULL) {
505 fh_log = fopen(log_file, "a");
506 if (fh_log == NULL && errno == ENOENT) {
507 char *log_dir = xdirname(log_file);
509 if (make_path(log_dir, 0755) < 0)
510 syserr(_("cannot create log directory '%s'"),
511 log_dir);
512 free(log_dir);
514 fh_log = fopen(log_file, "a");
516 if (fh_log == NULL && errno != EACCES)
517 syserr(_("cannot append to '%s'"), log_file);
520 if (fh_log) {
521 va_list args;
522 char timestamp[64];
523 time_t now;
524 struct tm tm;
526 time(&now);
527 if (localtime_r(&now, &tm) == NULL)
528 syserr(_("cannot get local time to log into '%s'"), log_file);
529 strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S",
530 &tm);
531 fprintf(fh_log, "%s %s: ", PROGNAME, timestamp);
532 va_start(args, fmt);
533 vfprintf(fh_log, fmt, args);
534 va_end(args);
535 fprintf(fh_log, "\n");
540 * Filesystem access for alternative handling.
543 static char *
544 fsys_get_path(const char *pathpart)
546 return xasprintf("%s%s", instdir, pathpart);
549 static const char *
550 fsys_set_dir(const char *dir)
552 if (dir == NULL) {
553 const char *instdir_env;
555 instdir_env = getenv(INSTDIR_ENVVAR);
556 if (instdir_env)
557 dir = instdir_env;
558 else
559 dir = "";
562 instdir_len = strlen(dir);
564 return dir;
567 static char *
568 fsys_gen_admindir(void)
570 return fsys_get_path(ADMINDIR "/alternatives");
573 static bool
574 fsys_pathname_is_missing(const char *pathname)
576 struct stat st;
577 char *root_pathname;
579 root_pathname = fsys_get_path(pathname);
581 errno = 0;
582 if (stat(root_pathname, &st) < 0 && errno != ENOENT)
583 syserr(_("cannot stat file '%s'"), root_pathname);
585 free(root_pathname);
587 if (errno == ENOENT)
588 return true;
590 return false;
593 static int
594 fsys_lstat(const char *linkname, struct stat *st)
596 char *root_linkname;
597 int rc;
599 root_linkname = fsys_get_path(linkname);
601 errno = 0;
602 rc = lstat(root_linkname, st);
604 free(root_linkname);
606 return rc;
609 static char *
610 fsys_areadlink(const char *linkname)
612 char *root_linkname;
613 char *target;
615 root_linkname = fsys_get_path(linkname);
616 target = areadlink(root_linkname);
617 free(root_linkname);
619 return target;
622 static char *
623 fsys_xreadlink(const char *linkname)
625 char *buf;
627 buf = fsys_areadlink(linkname);
628 if (buf == NULL)
629 syserr(_("unable to read link '%s%.255s'"), instdir, linkname);
631 return buf;
634 static void
635 fsys_set_ref_time(const char *linkname, const char *target)
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;
646 if (fsys_lstat(target, &st) < 0) {
647 if (errno != ENOENT)
648 syserr(_("unable to get file '%s%s' metadata"),
649 instdir, target);
650 return;
653 tv[0].tv_sec = st.st_mtime;
654 tv[0].tv_usec = 0;
655 tv[1].tv_sec = st.st_mtime;
656 tv[1].tv_usec = 0;
658 root_linkname = fsys_get_path(linkname);
659 if (lutimes(root_linkname, tv) < 0 && errno != ENOSYS)
660 syserr(_("cannot set symlink '%s' timestamp"), root_linkname);
661 free(root_linkname);
662 #endif
665 static void
666 fsys_symlink(const char *filename, const char *linkname)
668 char *root_linkname;
670 root_linkname = fsys_get_path(linkname);
672 if (unlink(root_linkname) < 0 && errno != ENOENT)
673 syserr(_("unable to remove '%s'"), root_linkname);
675 if (symlink(filename, root_linkname))
676 syserr(_("error creating symbolic link '%.255s'"), root_linkname);
678 free(root_linkname);
681 static void
682 fsys_mv(const char *src, const char *dst)
684 char *root_src;
685 char *root_dst;
687 root_src = fsys_get_path(src);
688 root_dst = fsys_get_path(dst);
690 xrename(root_src, root_dst);
692 free(root_src);
693 free(root_dst);
696 static void
697 fsys_rm(const char *f)
699 char *root_f;
701 root_f = fsys_get_path(f);
703 if (unlink(root_f) < 0 && errno != ENOENT)
704 syserr(_("unable to remove '%s'"), root_f);
706 free(root_f);
709 static void LIBCOMPAT_ATTR_PRINTF(1)
710 fsys_rm_args(const char *fmt, ...)
712 va_list args;
713 char *path;
715 va_start(args, fmt);
716 path = xvasprintf(fmt, args);
717 va_end(args);
719 fsys_rm(path);
720 free(path);
724 * OBJECTS
727 struct fileset {
728 struct fileset *next;
730 char *master_file;
731 int priority;
733 struct slave_file {
734 struct slave_file *next;
735 char *name;
736 char *file;
737 } *slaves;
740 static struct fileset *
741 fileset_new(const char *master_file, int prio)
743 struct fileset *fs;
745 fs = xmalloc(sizeof(*fs));
746 fs->next = NULL;
747 fs->master_file = xstrdup(master_file);
748 fs->priority = prio;
749 fs->slaves = NULL;
751 return fs;
754 static void
755 fileset_free(struct fileset *fs)
757 struct slave_file *slave, *next;
759 free(fs->master_file);
760 for (slave = fs->slaves; slave; slave = next) {
761 next = slave->next;
762 free(slave->name);
763 free(slave->file);
764 free(slave);
766 free(fs);
769 static void
770 fileset_add_slave(struct fileset *fs, const char *name, const char *file)
772 struct slave_file *sl, *cur, *prev = NULL;
774 /* Replace existing first */
775 for (cur = fs->slaves; cur; cur = cur->next) {
776 if (strcmp(cur->name, name) == 0) {
777 free(cur->file);
778 cur->file = xstrdup(file);
779 return;
781 prev = cur;
784 /* Otherwise add new at the end */
785 sl = xmalloc(sizeof(*sl));
786 sl->next = NULL;
787 sl->name = xstrdup(name);
788 sl->file = xstrdup(file);
789 if (prev)
790 prev->next = sl;
791 else
792 fs->slaves = sl;
795 static const char *
796 fileset_get_slave(struct fileset *fs, const char *name)
798 struct slave_file *slave;
800 for (slave = fs->slaves; slave; slave = slave->next) {
801 if (strcmp(slave->name, name) == 0)
802 return slave->file;
805 return NULL;
808 static bool
809 fileset_has_slave(struct fileset *fs, const char *name)
811 const char *file = fileset_get_slave(fs, name);
813 if (file == NULL)
814 return false;
816 return file[0] != '\0';
819 static bool
820 fileset_can_install_slave(struct fileset *fs, const char *slave_name)
822 /* Decide whether the slave alternative must be setup */
823 if (fileset_has_slave(fs, slave_name)) {
824 const char *slave = fileset_get_slave(fs, slave_name);
826 if (!fsys_pathname_is_missing(slave))
827 return true;
830 return false;
833 struct slave_link {
834 struct slave_link *next;
835 char *name;
836 char *link;
837 bool updated;
840 struct commit_operation {
841 struct commit_operation *next;
843 enum opcode {
844 OPCODE_NOP,
845 OPCODE_RM,
846 OPCODE_MV,
847 OPCODE_REF_TIME,
848 } opcode;
850 char *arg_a;
851 char *arg_b;
854 enum alternative_update_reason {
855 ALT_UPDATE_NO,
856 ALT_UPDATE_SLAVE_CHANGED,
857 ALT_UPDATE_LINK_BROKEN,
860 struct alternative {
861 char *master_name;
862 char *master_link;
863 char *current;
865 enum alternative_status {
866 ALT_ST_UNKNOWN,
867 ALT_ST_AUTO,
868 ALT_ST_MANUAL,
869 } status;
871 struct slave_link *slaves;
872 struct fileset *choices;
874 struct commit_operation *commit_ops;
876 int ref_count;
877 bool modified;
878 bool known_current;
881 static void
882 slave_link_free(struct slave_link *slave)
884 free(slave->name);
885 free(slave->link);
886 free(slave);
889 static void
890 commit_operation_free(struct commit_operation *commit_op)
892 free(commit_op->arg_a);
893 free(commit_op->arg_b);
894 free(commit_op);
897 static struct alternative *
898 alternative_new(const char *name)
900 struct alternative *alt;
902 alt = xmalloc(sizeof(*alt));
903 alt->master_name = xstrdup(name);
904 alt->master_link = NULL;
905 alt->current = NULL;
906 alt->status = ALT_ST_UNKNOWN;
907 alt->slaves = NULL;
908 alt->choices = NULL;
909 alt->commit_ops = NULL;
910 alt->modified = false;
911 alt->known_current = false;
912 alt->ref_count = 1;
914 return alt;
917 static inline void
918 alternative_ref(struct alternative *a)
920 a->ref_count++;
923 static inline bool
924 alternative_unref(struct alternative *a)
926 return --a->ref_count == 0;
929 static void
930 alternative_choices_free(struct alternative *a)
932 struct fileset *fs;
934 if (a->choices)
935 a->modified = true;
937 while (a->choices) {
938 fs = a->choices;
939 a->choices = fs->next;
940 fileset_free(fs);
944 static void
945 alternative_commit_operations_free(struct alternative *a)
947 struct commit_operation *op;
949 while (a->commit_ops) {
950 op = a->commit_ops;
951 a->commit_ops = op->next;
952 commit_operation_free(op);
956 static void
957 alternative_reset(struct alternative *alt)
959 struct slave_link *slave;
961 free(alt->current);
962 alt->current = NULL;
963 free(alt->master_link);
964 alt->master_link = NULL;
965 while (alt->slaves) {
966 slave = alt->slaves;
967 alt->slaves = slave->next;
968 slave_link_free(slave);
970 alternative_choices_free(alt);
971 alternative_commit_operations_free(alt);
972 alt->modified = false;
973 alt->known_current = false;
976 static void
977 alternative_free(struct alternative *alt)
979 if (!alternative_unref(alt))
980 return;
982 alternative_reset(alt);
983 free(alt->master_name);
984 free(alt);
987 static int
988 alternative_choices_count(struct alternative *alt)
990 struct fileset *fs;
991 int count = 0;
993 for (fs = alt->choices; fs; fs = fs->next)
994 count++;
996 return count;
999 static int
1000 alternative_slaves_count(struct alternative *alt)
1002 struct slave_link *sl;
1003 int count = 0;
1005 for (sl = alt->slaves; sl; sl = sl->next)
1006 count++;
1008 return count;
1011 static int
1012 compare_fileset(const void *va, const void *vb)
1014 const struct fileset *a = *(const struct fileset **)va;
1015 const struct fileset *b = *(const struct fileset **)vb;
1017 assert(a && a->master_file);
1018 assert(b && b->master_file);
1020 return strcmp(a->master_file, b->master_file);
1023 static int
1024 compare_slave_link(const void *va, const void *vb)
1026 const struct slave_link *a = *(const struct slave_link **)va;
1027 const struct slave_link *b = *(const struct slave_link **)vb;
1029 assert(a && a->name);
1030 assert(b && b->name);
1032 return strcmp(a->name, b->name);
1035 static void
1036 alternative_sort_choices(struct alternative *a)
1038 int count, i;
1039 struct fileset **table, *fs;
1041 count = alternative_choices_count(a);
1042 if (count < 2) /* Nothing to sort */
1043 return;
1045 /* Store objects in a table instead of a linked list */
1046 table = xmalloc(sizeof(fs) * count);
1047 for (fs = a->choices, i = 0; fs; fs = fs->next) {
1048 assert(fs->master_file);
1049 table[i++] = fs;
1052 qsort(table, count, sizeof(fs), compare_fileset);
1054 /* Rewrite the linked list from the sorted table */
1055 a->choices = fs = table[0];
1056 table[count - 1]->next = NULL;
1057 for (i = 1; i < count; fs = fs->next, i++)
1058 fs->next = table[i];
1059 free(table);
1062 static void
1063 alternative_sort_slaves(struct alternative *a)
1065 int count, i;
1066 struct slave_link **table, *sl;
1068 count = alternative_slaves_count(a);
1069 if (count < 2) /* Nothing to sort */
1070 return;
1072 /* Store objects in a table instead of a linked list */
1073 table = xmalloc(sizeof(sl) * count);
1074 for (sl = a->slaves, i = 0; sl; sl = sl->next, i++) {
1075 table[i] = sl;
1078 qsort(table, count, sizeof(sl), compare_slave_link);
1080 /* Rewrite the linked list from the sorted table */
1081 a->slaves = sl = table[0];
1082 table[count - 1]->next = NULL;
1083 for (i = 1; i < count; sl = sl->next, i++)
1084 sl->next = table[i];
1085 free(table);
1088 static struct fileset *
1089 alternative_get_fileset(struct alternative *a, const char *file)
1091 struct fileset *fs;
1093 for (fs = a->choices; fs; fs = fs->next)
1094 if (strcmp(fs->master_file, file) == 0)
1095 return fs;
1097 return NULL;
1100 static struct slave_link *
1101 alternative_get_slave(struct alternative *a, const char *name)
1103 struct slave_link *sl;
1105 for (sl = a->slaves; sl; sl = sl->next)
1106 if (strcmp(sl->name, name) == 0)
1107 return sl;
1109 return NULL;
1112 static bool
1113 alternative_has_slave(struct alternative *a, const char *name)
1115 return alternative_get_slave(a, name) != NULL;
1118 static bool
1119 alternative_has_choice(struct alternative *a, const char *file)
1121 return alternative_get_fileset(a, file) != NULL;
1124 static void
1125 alternative_add_choice(struct alternative *a, struct fileset *fs)
1127 struct fileset *cur, *prev = NULL;
1129 /* Replace if already existing */
1130 for (cur = a->choices; cur; cur = cur->next) {
1131 if (strcmp(cur->master_file, fs->master_file) == 0) {
1132 fs->next = cur->next;
1133 fileset_free(cur);
1134 if (prev)
1135 prev->next = fs;
1136 else
1137 a->choices = fs;
1139 /* XXX: Be smarter in detecting change? */
1140 a->modified = true;
1141 return;
1143 prev = cur;
1146 /* Otherwise add at the end */
1147 if (prev == NULL)
1148 a->choices = fs;
1149 else
1150 prev->next = fs;
1151 fs->next = NULL;
1152 a->modified = true;
1155 static struct slave_link *
1156 alternative_add_slave(struct alternative *a,
1157 const char *slave_name, const char *slave_link)
1159 struct slave_link *sl, *new;
1161 /* Replace if already existing */
1162 for (sl = a->slaves; sl; sl = sl->next) {
1163 if (strcmp(sl->name, slave_name) == 0) {
1164 free(sl->link);
1165 sl->link = xstrdup(slave_link);
1166 return sl;
1168 if (sl->next == NULL)
1169 break;
1172 /* Otherwise create new and add at the end */
1173 new = xmalloc(sizeof(*new));
1174 new->name = xstrdup(slave_name);
1175 new->link = xstrdup(slave_link);
1176 new->updated = false;
1177 new->next = NULL;
1178 if (sl)
1179 sl->next = new;
1180 else
1181 a->slaves = new;
1183 return new;
1186 static void
1187 alternative_copy_slave(struct alternative *a, struct slave_link *sl)
1189 struct slave_link *sl_new;
1191 sl_new = alternative_add_slave(a, sl->name, sl->link);
1192 sl_new->updated = sl->updated;
1195 static const char *
1196 alternative_status_string(enum alternative_status status)
1198 return (status == ALT_ST_AUTO) ? "auto" : "manual";
1201 static const char *
1202 alternative_status_describe(enum alternative_status status)
1204 return (status == ALT_ST_AUTO) ? _("auto mode") : _("manual mode");
1207 static void
1208 alternative_set_status(struct alternative *a, enum alternative_status status)
1210 if (a->status == ALT_ST_UNKNOWN || status != a->status)
1211 a->modified = true;
1213 if (a->status != ALT_ST_UNKNOWN && status != a->status)
1214 log_msg("status of link group %s set to %s", a->master_link,
1215 alternative_status_string(status));
1217 a->status = status;
1220 static void
1221 alternative_set_link(struct alternative *a, const char *linkname)
1223 if (a->master_link == NULL || strcmp(linkname, a->master_link) != 0)
1224 a->modified = true;
1226 free(a->master_link);
1227 a->master_link = xstrdup(linkname);
1230 static bool
1231 alternative_remove_choice(struct alternative *a, const char *file)
1233 struct fileset *fs, *fs_prev;
1235 fs_prev = NULL;
1236 for (fs = a->choices; fs; fs = fs->next) {
1237 if (strcmp(fs->master_file, file) != 0) {
1238 fs_prev = fs;
1239 continue;
1241 if (fs_prev)
1242 fs_prev->next = fs->next;
1243 else
1244 a->choices = fs->next;
1245 fileset_free(fs);
1246 a->modified = true;
1247 return true;
1250 return false;
1254 * Alternatives Database Load/Store functions.
1257 enum LIBCOMPAT_ATTR_ENUM_FLAGS altdb_flags {
1258 ALTDB_LAX_PARSER = 1 << 0,
1259 ALTDB_WARN_PARSER = 1 << 1,
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;
1272 static void
1273 altdb_context_free(struct altdb_context *ctx)
1275 if (ctx->fh)
1276 fclose(ctx->fh);
1277 free(ctx->filename);
1280 static void LIBCOMPAT_ATTR_NORET LIBCOMPAT_ATTR_PRINTF(2)
1281 altdb_bad_format(struct altdb_context *ctx, const char *format, ...)
1283 va_list args;
1284 char *msg;
1286 va_start(args, format);
1287 msg = xvasprintf(format, args);
1288 va_end(args);
1290 ctx->bad_format(ctx, msg);
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");
1297 static int
1298 altdb_filter_namelist(const struct dirent *entry)
1300 if (strcmp(entry->d_name, ".") == 0 ||
1301 strcmp(entry->d_name, "..") == 0 ||
1302 (strlen(entry->d_name) > strlen(ALT_TMP_EXT) &&
1303 strcmp(entry->d_name + strlen(entry->d_name) -
1304 strlen(ALT_TMP_EXT), ALT_TMP_EXT) == 0))
1305 return 0;
1306 return 1;
1309 static int
1310 altdb_get_namelist(struct dirent ***table)
1312 int count;
1314 count = scandir(admdir, table, altdb_filter_namelist, alphasort);
1315 if (count < 0) {
1316 if (errno != ENOENT)
1317 syserr(_("cannot scan directory '%.255s'"), admdir);
1318 /* The directory does not exist, proceed anyway. */
1319 *table = NULL;
1320 count = 0;
1323 return count;
1326 static void
1327 altdb_free_namelist(struct dirent **table, int n)
1329 while (n--)
1330 free(table[n]);
1331 free(table);
1334 static char *
1335 altdb_get_line(struct altdb_context *ctx, const char *name)
1337 char *buf, *line;
1338 size_t len, bufsz, i;
1340 bufsz = 1024;
1341 buf = xmalloc(bufsz);
1343 for (i = 0; true; i += strlen(line)) {
1344 errno = 0;
1345 line = fgets(buf + i, bufsz - i, ctx->fh);
1346 if (line) {
1347 if (strlen(buf) < bufsz - 1 || buf[bufsz - 2] == '\n')
1348 break;
1349 /* Need more space */
1350 bufsz *= 2;
1351 buf = realloc(buf, bufsz);
1352 if (!buf)
1353 error(_("failed to allocate memory"));
1354 continue;
1356 if (feof(ctx->fh))
1357 altdb_bad_format(ctx, _("unexpected end of file while trying "
1358 "to read %s"), name);
1359 altdb_bad_format(ctx, _("while reading %s: %s"),
1360 name, strerror(errno));
1363 len = strlen(buf);
1364 if (len == 0 || buf[len - 1] != '\n') {
1365 altdb_bad_format(ctx, _("line not terminated while trying "
1366 "to read %s"), name);
1368 line[len - 1] = '\0';
1370 return buf;
1373 static void LIBCOMPAT_ATTR_NORET
1374 altdb_parse_error(struct altdb_context *ctx, const char *msg)
1376 error(_("%s corrupt: %s"), ctx->filename, msg);
1379 static void LIBCOMPAT_ATTR_NORET
1380 altdb_parse_stop(struct altdb_context *ctx, const char *msg)
1382 longjmp(ctx->on_error, 1);
1385 static void
1386 altdb_print_line(struct altdb_context *ctx, const char *line)
1388 if (strchr(line, '\n') != NULL)
1389 error(_("newlines prohibited in update-alternatives files (%s)"),
1390 line);
1392 if (fprintf(ctx->fh, "%s\n", line) < (int) strlen(line) + 1)
1393 syserr(_("unable to write file '%s'"), ctx->filename);
1396 static bool
1397 alternative_parse_slave(struct alternative *a, struct altdb_context *ctx)
1399 char *name, *linkname;
1400 struct slave_link *sl;
1402 name = altdb_get_line(ctx, _("slave name"));
1403 if (!strlen(name)) { /* End of list */
1404 free(name);
1405 return false;
1407 sl = alternative_get_slave(a, name);
1408 if (sl) {
1409 free(name);
1410 altdb_bad_format(ctx, _("duplicate slave name %s"), sl->name);
1413 linkname = altdb_get_line(ctx, _("slave link"));
1414 if (strcmp(linkname, a->master_link) == 0) {
1415 free(linkname);
1416 free(name);
1417 altdb_bad_format(ctx, _("slave link same as main link %s"),
1418 a->master_link);
1420 for (sl = a->slaves; sl; sl = sl->next) {
1421 if (strcmp(linkname, sl->link) == 0) {
1422 free(linkname);
1423 free(name);
1424 altdb_bad_format(ctx, _("duplicate slave link %s"),
1425 sl->link);
1429 alternative_add_slave(a, name, linkname);
1430 free(linkname);
1431 free(name);
1433 return true;
1436 static bool
1437 alternative_parse_fileset(struct alternative *a, struct altdb_context *ctx)
1439 struct fileset *fs;
1440 struct slave_link *sl;
1441 char *master_file;
1443 master_file = altdb_get_line(ctx, _("master file"));
1444 if (!strlen(master_file)) { /* End of list */
1445 free(master_file);
1446 return false;
1449 fs = alternative_get_fileset(a, master_file);
1450 if (fs)
1451 altdb_bad_format(ctx, _("duplicate path %s"), master_file);
1453 if (fsys_pathname_is_missing(master_file)) {
1454 char *junk;
1456 /* File not found - remove. */
1457 if (ctx->flags & ALTDB_WARN_PARSER)
1458 warning(_("alternative %s (part of link group %s) "
1459 "doesn't exist; removing from list of "
1460 "alternatives"), master_file, a->master_name);
1461 junk = altdb_get_line(ctx, _("priority"));
1462 free(junk);
1463 for (sl = a->slaves; sl; sl = sl->next) {
1464 junk = altdb_get_line(ctx, _("slave file"));
1465 free(junk);
1467 ctx->modified = true;
1468 } else {
1469 char *prio_str, *prio_end;
1470 long prio;
1472 prio_str = altdb_get_line(ctx, _("priority"));
1473 errno = 0;
1474 prio = strtol(prio_str, &prio_end, 10);
1475 /* XXX: Leak master_file/prio_str on non-fatal error */
1476 if (prio_str == prio_end || *prio_end != '\0')
1477 altdb_bad_format(ctx, _("priority of %s: %s"),
1478 master_file, prio_str);
1479 if (prio < INT_MIN || prio > INT_MAX || errno == ERANGE)
1480 altdb_bad_format(ctx,
1481 _("priority of %s is out of range: %s"),
1482 master_file, prio_str);
1483 free(prio_str);
1485 fs = fileset_new(master_file, prio);
1486 for (sl = a->slaves; sl; sl = sl->next) {
1487 char *slave_file = altdb_get_line(ctx, _("slave file"));
1488 fileset_add_slave(fs, sl->name, slave_file);
1489 free(slave_file);
1491 alternative_add_choice(a, fs);
1493 free(master_file);
1495 return true;
1498 static bool
1499 alternative_load(struct alternative *a, enum altdb_flags flags)
1501 struct altdb_context ctx;
1502 struct stat st;
1503 char *status;
1504 char *master_link;
1506 /* Initialize parse context */
1507 ctx.modified = false;
1508 ctx.flags = flags;
1509 if (flags & ALTDB_LAX_PARSER)
1510 ctx.bad_format = altdb_parse_stop;
1511 else
1512 ctx.bad_format = altdb_parse_error;
1513 ctx.filename = xasprintf("%s/%s", admdir, a->master_name);
1515 /* Open the alternative file. */
1516 ctx.fh = fopen(ctx.filename, "r");
1517 if (ctx.fh == NULL) {
1518 if (errno == ENOENT) {
1519 altdb_context_free(&ctx);
1520 return false;
1523 syserr(_("unable to open file '%s'"), ctx.filename);
1526 if (setjmp(ctx.on_error)) {
1527 altdb_context_free(&ctx);
1528 alternative_reset(a);
1529 return false;
1532 /* Verify the alternative is not empty. */
1533 if (fstat(fileno(ctx.fh), &st) < 0)
1534 syserr(_("cannot stat file '%s'"), ctx.filename);
1535 if (st.st_size == 0) {
1536 altdb_context_free(&ctx);
1537 alternative_reset(a);
1538 return false;
1541 /* Start parsing mandatory attributes (link+status) of the alternative */
1542 alternative_reset(a);
1543 status = altdb_get_line(&ctx, _("status"));
1544 if (strcmp(status, "auto") != 0 && strcmp(status, "manual") != 0)
1545 altdb_bad_format(&ctx, _("invalid status"));
1546 alternative_set_status(a, (strcmp(status, "auto") == 0) ?
1547 ALT_ST_AUTO : ALT_ST_MANUAL);
1548 free(status);
1550 master_link = altdb_get_line(&ctx, _("master link"));
1551 alternative_set_link(a, master_link);
1552 free(master_link);
1554 /* Parse the description of the slaves links of the alternative */
1555 while (alternative_parse_slave(a, &ctx));
1557 /* Parse the available choices in the alternative */
1558 while (alternative_parse_fileset(a, &ctx)) ;
1560 /* Close database file */
1561 if (fclose(ctx.fh))
1562 syserr(_("unable to close file '%s'"), ctx.filename);
1563 free(ctx.filename);
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 a->modified = ctx.modified;
1570 return true;
1573 static void
1574 alternative_save(struct alternative *a)
1576 struct altdb_context ctx;
1577 struct slave_link *sl, *sl_prev;
1578 struct fileset *fs;
1579 char *filenew, *file;
1581 /* Cleanup unused slaves before writing admin file. */
1582 sl_prev = NULL;
1583 sl = a->slaves;
1584 while (sl) {
1585 bool has_slave = false;
1587 for (fs = a->choices; fs; fs = fs->next) {
1588 if (fileset_has_slave(fs, sl->name)) {
1589 has_slave = true;
1590 break;
1594 if (!has_slave) {
1595 struct slave_link *sl_rm;
1597 verbose(_("discarding obsolete slave link %s (%s)"),
1598 sl->name, sl->link);
1599 if (sl_prev)
1600 sl_prev->next = sl->next;
1601 else
1602 a->slaves = sl->next;
1603 sl_rm = sl;
1604 sl = sl->next;
1605 slave_link_free(sl_rm);
1606 } else {
1607 sl_prev = sl;
1608 sl = sl->next;
1612 /* Sort entries */
1613 alternative_sort_slaves(a);
1614 alternative_sort_choices(a);
1616 /* Write admin file. */
1617 file = xasprintf("%s/%s", admdir, a->master_name);
1618 filenew = xasprintf("%s" ALT_TMP_EXT, file);
1620 ctx.filename = filenew;
1621 ctx.fh = fopen(ctx.filename, "w");
1622 if (ctx.fh == NULL && errno == ENOENT) {
1623 if (make_path(admdir, 0755) < 0)
1624 syserr(_("cannot create administrative directory '%s'"),
1625 admdir);
1626 ctx.fh = fopen(ctx.filename, "w");
1628 if (ctx.fh == NULL)
1629 syserr(_("unable to create file '%s'"), ctx.filename);
1631 altdb_print_line(&ctx, alternative_status_string(a->status));
1632 altdb_print_line(&ctx, a->master_link);
1633 for (sl = a->slaves; sl; sl = sl->next) {
1634 altdb_print_line(&ctx, sl->name);
1635 altdb_print_line(&ctx, sl->link);
1637 altdb_print_line(&ctx, "");
1639 for (fs = a->choices; fs; fs = fs->next) {
1640 char *prio;
1642 altdb_print_line(&ctx, fs->master_file);
1644 prio = xasprintf("%d", fs->priority);
1645 altdb_print_line(&ctx, prio);
1646 free(prio);
1648 for (sl = a->slaves; sl; sl = sl->next) {
1649 if (fileset_has_slave(fs, sl->name))
1650 altdb_print_line(&ctx,
1651 fileset_get_slave(fs, sl->name));
1652 else
1653 altdb_print_line(&ctx, "");
1656 altdb_print_line(&ctx, "");
1658 /* Close database file */
1659 if (fflush(ctx.fh))
1660 syserr(_("unable to flush file '%s'"), ctx.filename);
1661 if (fsync(fileno(ctx.fh)))
1662 syserr(_("unable to sync file '%s'"), ctx.filename);
1663 if (fclose(ctx.fh))
1664 syserr(_("unable to close file '%s'"), ctx.filename);
1666 /* Put in place atomically. */
1667 xrename(filenew, file);
1669 free(filenew);
1670 free(file);
1673 static const char *
1674 alternative_set_current(struct alternative *a, char *new_choice)
1676 a->known_current = true;
1677 a->current = new_choice;
1679 return new_choice;
1682 static const char *
1683 alternative_get_current(struct alternative *a)
1685 char *curlink;
1686 char *file;
1688 if (a->known_current)
1689 return a->current;
1691 curlink = xasprintf("%s/%s", altdir, a->master_name);
1692 file = fsys_areadlink(curlink);
1693 if (file == NULL && errno != ENOENT)
1694 syserr(_("cannot stat file '%s%s'"), instdir, curlink);
1695 free(curlink);
1697 return alternative_set_current(a, file);
1700 static struct fileset *
1701 alternative_get_best(struct alternative *a)
1703 struct fileset *fs, *best;
1704 const char *current;
1706 current = alternative_get_current(a);
1707 if (current)
1708 best = alternative_get_fileset(a, current);
1709 else
1710 best = NULL;
1712 if (best == NULL)
1713 best = a->choices;
1715 for (fs = a->choices; fs; fs = fs->next)
1716 if (fs->priority > best->priority)
1717 best = fs;
1719 return best;
1722 static void
1723 alternative_display_query(struct alternative *a)
1725 struct fileset *best, *fs;
1726 struct slave_link *sl;
1727 const char *current;
1729 pr("Name: %s", a->master_name);
1730 pr("Link: %s", a->master_link);
1731 if (alternative_slaves_count(a) > 0) {
1732 pr("Slaves:");
1733 for (sl = a->slaves; sl; sl = sl->next)
1734 pr(" %s %s", sl->name, sl->link);
1736 pr("Status: %s", alternative_status_string(a->status));
1737 best = alternative_get_best(a);
1738 if (best)
1739 pr("Best: %s", best->master_file);
1740 current = alternative_get_current(a);
1741 pr("Value: %s", current ? current : "none");
1743 for (fs = a->choices; fs; fs = fs->next) {
1744 printf("\n");
1745 pr("Alternative: %s", fs->master_file);
1746 pr("Priority: %d", fs->priority);
1747 if (alternative_slaves_count(a) == 0)
1748 continue;
1749 pr("Slaves:");
1750 for (sl = a->slaves; sl; sl = sl->next) {
1751 if (fileset_has_slave(fs, sl->name))
1752 pr(" %s %s", sl->name,
1753 fileset_get_slave(fs, sl->name));
1758 static void
1759 alternative_display_user(struct alternative *a)
1761 const char *current;
1762 struct fileset *fs;
1763 struct slave_link *sl;
1765 pr("%s - %s", a->master_name, alternative_status_describe(a->status));
1766 fs = alternative_get_best(a);
1767 if (fs)
1768 pr(_(" link best version is %s"), fs->master_file);
1769 else
1770 pr(_(" link best version not available"));
1771 current = alternative_get_current(a);
1772 if (current) {
1773 pr(_(" link currently points to %s"), current);
1774 } else {
1775 pr(_(" link currently absent"));
1777 pr(_(" link %s is %s"), a->master_name, a->master_link);
1778 for (sl = a->slaves; sl; sl = sl->next)
1779 pr(_(" slave %s is %s"), sl->name, sl->link);
1781 for (fs = a->choices; fs; fs = fs->next) {
1782 pr(_("%s - priority %d"), fs->master_file, fs->priority);
1783 for (sl = a->slaves; sl; sl = sl->next) {
1784 if (fileset_has_slave(fs, sl->name))
1785 pr(_(" slave %s: %s"), sl->name,
1786 fileset_get_slave(fs, sl->name));
1791 static void
1792 alternative_display_list(struct alternative *a)
1794 struct fileset *fs;
1796 for (fs = a->choices; fs; fs = fs->next)
1797 pr("%s", fs->master_file);
1800 static void
1801 alternative_print_choice(struct alternative *a, enum alternative_status status,
1802 struct fileset *fs, int idx, int len)
1804 const char *current = alternative_get_current(a);
1805 int mark;
1807 if (a->status == status &&
1808 current && strcmp(current, fs->master_file) == 0)
1809 mark = '*';
1810 else
1811 mark = ' ';
1813 pr("%c %-12d %-*s % -10d %s", mark, idx, len,
1814 fs->master_file, fs->priority, alternative_status_describe(status));
1817 static char *
1818 alternative_select_choice(struct alternative *a)
1820 const char *current;
1821 char *ret, selection[_POSIX_PATH_MAX];
1822 struct fileset *best, *fs;
1823 int n_choices;
1824 int len;
1826 n_choices = alternative_choices_count(a);
1827 current = alternative_get_current(a);
1828 best = alternative_get_best(a);
1829 assert(best);
1831 len = 15;
1832 for (fs = a->choices; fs; fs = fs->next)
1833 len = max(len, (int)strlen(fs->master_file) + 1);
1835 for (;;) {
1836 int idx;
1838 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 printf("\n");
1843 pr(" %-12.12s %-*.*s %-10.10s %s", _("Selection"), len, len,
1844 _("Path"), _("Priority"), _("Status"));
1845 pr("------------------------------------------------------------");
1846 idx = 0;
1847 alternative_print_choice(a, ALT_ST_AUTO, best, idx++, len);
1848 for (fs = a->choices; fs; fs = fs->next, idx++)
1849 alternative_print_choice(a, ALT_ST_MANUAL, fs, idx, len);
1850 printf("\n");
1851 printf(_("Press <enter> to keep the current choice[*], "
1852 "or type selection number: "));
1853 ret = fgets(selection, sizeof(selection), stdin);
1854 if (ret == NULL || strlen(selection) == 0) {
1855 return NULL;
1857 selection[strlen(selection) - 1] = '\0';
1858 if (strlen(selection) == 0)
1859 return xstrdup(current);
1860 errno = 0;
1861 idx = strtol(selection, &ret, 10);
1862 if (idx >= 0 && errno == 0 && *ret == '\0') {
1863 /* Look up by index */
1864 if (idx == 0) {
1865 alternative_set_status(a, ALT_ST_AUTO);
1866 return xstrdup(best->master_file);
1868 idx--;
1869 for (fs = a->choices; idx && fs; idx--)
1870 fs = fs->next;
1871 if (fs) {
1872 alternative_set_status(a, ALT_ST_MANUAL);
1873 return xstrdup(fs->master_file);
1875 } else {
1876 /* Look up by name */
1877 fs = alternative_get_fileset(a, selection);
1878 if (fs) {
1879 alternative_set_status(a, ALT_ST_MANUAL);
1880 return xstrdup(selection);
1886 static char *
1887 alternative_config(struct alternative *a, const char *current_choice)
1889 char *new_choice = NULL;
1891 if (alternative_choices_count(a) == 0) {
1892 pr(_("There is no program which provides %s."),
1893 a->master_name);
1894 pr(_("Nothing to configure."));
1895 } else if (opt_skip_auto && a->status == ALT_ST_AUTO) {
1896 alternative_display_user(a);
1897 } else {
1898 new_choice = alternative_select_choice(a);
1901 return new_choice;
1904 static void
1905 alternative_add_commit_op(struct alternative *a, enum opcode opcode,
1906 const char *arg_a, const char *arg_b)
1908 struct commit_operation *op, *cur;
1910 op = xmalloc(sizeof(*op));
1911 op->opcode = opcode;
1912 op->arg_a = xstrdup(arg_a);
1913 op->arg_b = xstrdup(arg_b);
1914 op->next = NULL;
1916 /* Add at the end */
1917 cur = a->commit_ops;
1918 while (cur && cur->next)
1919 cur = cur->next;
1920 if (cur)
1921 cur->next = op;
1922 else
1923 a->commit_ops = op;
1926 static void
1927 alternative_commit(struct alternative *a)
1929 struct commit_operation *op;
1931 for (op = a->commit_ops; op; op = op->next) {
1932 switch (op->opcode) {
1933 case OPCODE_NOP:
1934 break;
1935 case OPCODE_RM:
1936 fsys_rm(op->arg_a);
1937 break;
1938 case OPCODE_MV:
1939 fsys_mv(op->arg_a, op->arg_b);
1940 break;
1941 case OPCODE_REF_TIME:
1942 fsys_set_ref_time(op->arg_a, op->arg_b);
1943 break;
1947 alternative_commit_operations_free(a);
1950 enum alternative_path_status {
1951 ALT_PATH_SYMLINK,
1952 ALT_PATH_MISSING,
1953 ALT_PATH_OTHER,
1956 static enum alternative_path_status
1957 alternative_path_classify(const char *linkname)
1959 struct stat st;
1961 if (fsys_lstat(linkname, &st) < 0) {
1962 if (errno != ENOENT)
1963 syserr(_("cannot stat file '%s%s'"), instdir, linkname);
1964 return ALT_PATH_MISSING;
1965 } else if (S_ISLNK(st.st_mode)) {
1966 return ALT_PATH_SYMLINK;
1967 } else {
1968 return ALT_PATH_OTHER;
1972 static bool
1973 alternative_path_can_remove(const char *linkname)
1975 if (opt_force)
1976 return true;
1978 if (alternative_path_classify(linkname) == ALT_PATH_OTHER)
1979 return false;
1980 else
1981 return true;
1984 static bool
1985 alternative_path_needs_update(const char *linkname, const char *filename)
1987 char *linktarget;
1988 bool update;
1990 if (opt_force)
1991 return true;
1993 switch (alternative_path_classify(linkname)) {
1994 case ALT_PATH_SYMLINK:
1995 linktarget = fsys_xreadlink(linkname);
1996 if (strcmp(linktarget, filename) == 0)
1997 update = false;
1998 else
1999 update = true;
2000 free(linktarget);
2002 return update;
2003 case ALT_PATH_OTHER:
2004 warning(_("not replacing %s with a link"), linkname);
2005 return false;
2006 case ALT_PATH_MISSING:
2007 default:
2008 return true;
2012 static void
2013 alternative_prepare_install_single(struct alternative *a, const char *name,
2014 const char *linkname, const char *file)
2016 char *fntmp, *fn;
2018 /* Create alternatives directory (/etc/alternatives) if missing. */
2019 if (fsys_pathname_is_missing(altdir)) {
2020 char *root_altdir = fsys_get_path(altdir);
2022 if (make_path(root_altdir, 0755) < 0)
2023 syserr(_("cannot create alternatives directory '%s'"),
2024 root_altdir);
2026 free(root_altdir);
2029 fn = xasprintf("%s/%s", altdir, name);
2031 /* Create link in /etc/alternatives. */
2032 fntmp = xasprintf("%s/%s" ALT_TMP_EXT, altdir, name);
2033 fsys_symlink(file, fntmp);
2034 alternative_add_commit_op(a, OPCODE_MV, fntmp, fn);
2035 if (fsys_pathname_is_missing(fn))
2036 alternative_add_commit_op(a, OPCODE_REF_TIME, fn, file);
2037 free(fntmp);
2039 if (alternative_path_needs_update(linkname, fn)) {
2040 /* Create alternative link. */
2041 fntmp = xasprintf("%s" ALT_TMP_EXT, linkname);
2042 fsys_symlink(fn, fntmp);
2043 alternative_add_commit_op(a, OPCODE_MV, fntmp, linkname);
2044 if (fsys_pathname_is_missing(linkname))
2045 alternative_add_commit_op(a, OPCODE_REF_TIME, linkname, fn);
2046 free(fntmp);
2048 free(fn);
2051 static void
2052 alternative_prepare_install(struct alternative *a, const char *choice)
2054 struct slave_link *sl;
2055 struct fileset *fs;
2057 fs = alternative_get_fileset(a, choice);
2058 if (fs == NULL)
2059 error(_("can't install unknown choice %s"), choice);
2061 /* Take care of master alternative */
2062 alternative_prepare_install_single(a, a->master_name, a->master_link,
2063 choice);
2065 /* Take care of slaves alternatives */
2066 for (sl = a->slaves; sl; sl = sl->next) {
2067 char *fn;
2069 if (fileset_can_install_slave(fs, sl->name)) {
2070 alternative_prepare_install_single(a, sl->name,
2071 sl->link, fileset_get_slave(fs, sl->name));
2072 continue;
2075 /* Slave can't be installed */
2076 if (fileset_has_slave(fs, sl->name))
2077 warning(_("skip creation of %s because associated "
2078 "file %s (of link group %s) doesn't exist"),
2079 sl->link, fileset_get_slave(fs, sl->name),
2080 a->master_name);
2082 /* Drop unused slave. */
2083 fn = xasprintf("%s/%s", altdir, sl->name);
2084 if (alternative_path_can_remove(sl->link))
2085 alternative_add_commit_op(a, OPCODE_RM, sl->link, NULL);
2086 else
2087 warning(_("not removing %s since it's not a symlink"),
2088 sl->link);
2089 alternative_add_commit_op(a, OPCODE_RM, fn, NULL);
2090 free(fn);
2094 static void
2095 alternative_remove_files(struct alternative *a)
2097 struct slave_link *sl;
2099 fsys_rm_args("%s" ALT_TMP_EXT, a->master_link);
2100 if (alternative_path_can_remove(a->master_link))
2101 fsys_rm(a->master_link);
2103 fsys_rm_args("%s/%s" ALT_TMP_EXT, altdir, a->master_name);
2104 fsys_rm_args("%s/%s", altdir, a->master_name);
2106 for (sl = a->slaves; sl; sl = sl->next) {
2107 fsys_rm_args("%s" ALT_TMP_EXT, sl->link);
2108 if (alternative_path_can_remove(sl->link))
2109 fsys_rm(sl->link);
2111 fsys_rm_args("%s/%s" ALT_TMP_EXT, altdir, sl->name);
2112 fsys_rm_args("%s/%s", altdir, sl->name);
2114 /* Drop admin file */
2115 xunlink_args("%s/%s", admdir, a->master_name);
2118 static char *
2119 alternative_remove(struct alternative *a, const char *current_choice,
2120 const char *path)
2122 char *new_choice = NULL;
2124 if (alternative_has_choice(a, path))
2125 alternative_remove_choice(a, path);
2126 else
2127 verbose(_("alternative %s for %s not registered; not removing"),
2128 path, a->master_name);
2130 if (current_choice && strcmp(current_choice, path) == 0) {
2131 struct fileset *best;
2133 /* Current choice is removed. */
2134 if (a->status == ALT_ST_MANUAL) {
2135 /* And it was manual, switch to auto. */
2136 info(_("removing manually selected alternative "
2137 "- switching %s to auto mode"),
2138 a->master_name);
2139 alternative_set_status(a, ALT_ST_AUTO);
2141 best = alternative_get_best(a);
2142 if (best)
2143 new_choice = xstrdup(best->master_file);
2146 return new_choice;
2149 static bool
2150 alternative_has_broken_symlink(const char *linkname, const char *ref_target)
2152 char *target;
2154 target = fsys_areadlink(linkname);
2155 if (!target)
2156 return true;
2157 if (strcmp(target, ref_target) != 0) {
2158 free(target);
2159 return true;
2161 free(target);
2162 return false;
2165 static bool
2166 alternative_has_broken_slave(struct slave_link *sl, struct fileset *fs)
2168 if (fileset_can_install_slave(fs, sl->name)) {
2169 char *wanted;
2170 const char *sl_target;
2172 /* Verify link -> /etc/alternatives/foo */
2173 wanted = xasprintf("%s/%s", altdir, sl->name);
2174 if (alternative_has_broken_symlink(sl->link, wanted)) {
2175 free(wanted);
2176 return true;
2179 /* Verify /etc/alternatives/foo -> file */
2180 sl_target = fileset_get_slave(fs, sl->name);
2181 if (alternative_has_broken_symlink(wanted, sl_target)) {
2182 free(wanted);
2183 return true;
2186 free(wanted);
2187 } else {
2188 char *sl_altlnk;
2190 /* Slave link must not exist. */
2191 if (alternative_path_classify(sl->link) != ALT_PATH_MISSING)
2192 return true;
2193 sl_altlnk = xasprintf("%s/%s", altdir, sl->name);
2194 if (alternative_path_classify(sl_altlnk) != ALT_PATH_MISSING) {
2195 free(sl_altlnk);
2196 return true;
2198 free(sl_altlnk);
2201 return false;
2204 static enum alternative_update_reason
2205 alternative_needs_update(struct alternative *a)
2207 enum alternative_update_reason reason = ALT_UPDATE_NO;
2208 const char *current;
2209 char *wanted;
2210 struct fileset *fs;
2211 struct slave_link *sl;
2213 /* Check master link */
2214 wanted = xasprintf("%s/%s", altdir, a->master_name);
2215 if (alternative_has_broken_symlink(a->master_link, wanted)) {
2216 free(wanted);
2217 return ALT_UPDATE_LINK_BROKEN;
2219 free(wanted);
2221 /* Stop if we have an unmanaged alternative */
2222 current = alternative_get_current(a);
2223 if (current == NULL)
2224 return ALT_UPDATE_LINK_BROKEN;
2226 fs = alternative_get_fileset(a, current);
2228 /* Stop if we do not have the choice. */
2229 if (fs == NULL)
2230 return ALT_UPDATE_NO;
2232 /* Check slaves */
2233 for (sl = a->slaves; sl; sl = sl->next) {
2234 if (alternative_has_broken_slave(sl, fs)) {
2235 if (sl->updated)
2236 reason = ALT_UPDATE_SLAVE_CHANGED;
2237 else
2238 return ALT_UPDATE_LINK_BROKEN;
2242 return reason;
2245 struct alternative_map {
2246 struct alternative_map *next;
2248 const char *key;
2249 struct alternative *item;
2252 static struct alternative_map *
2253 alternative_map_new(const char *key, struct alternative *a)
2255 struct alternative_map *am;
2257 am = xmalloc(sizeof(*am));
2258 am->next = NULL;
2259 am->key = key;
2260 am->item = a;
2262 return am;
2265 static struct alternative *
2266 alternative_map_find(struct alternative_map *am, const char *key)
2268 for (; am; am = am->next)
2269 if (am->key && strcmp(am->key, key) == 0)
2270 return am->item;
2272 return NULL;
2275 static void
2276 alternative_map_add(struct alternative_map *am, const char *key,
2277 struct alternative *a)
2279 alternative_ref(a);
2281 if (am->key == NULL) {
2282 am->key = key;
2283 am->item = a;
2284 } else {
2285 struct alternative_map *new = alternative_map_new(key, a);
2287 while (am->next)
2288 am = am->next;
2289 am->next = new;
2293 static void
2294 alternative_map_load_names(struct alternative_map *alt_map_obj)
2296 struct dirent **table;
2297 int i, count;
2299 count = altdb_get_namelist(&table);
2300 for (i = 0; i < count; i++) {
2301 struct alternative *a_new = alternative_new(table[i]->d_name);
2303 if (!alternative_load(a_new, ALTDB_LAX_PARSER)) {
2304 alternative_free(a_new);
2305 continue;
2307 alternative_map_add(alt_map_obj, a_new->master_name, a_new);
2309 alternative_unref(a_new);
2311 altdb_free_namelist(table, count);
2314 static void
2315 alternative_map_load_tree(struct alternative_map *alt_map_links,
2316 struct alternative_map *alt_map_parent)
2318 struct dirent **table;
2319 int i, count;
2321 count = altdb_get_namelist(&table);
2322 for (i = 0; i < count; i++) {
2323 struct slave_link *sl;
2324 struct alternative *a_new = alternative_new(table[i]->d_name);
2326 if (!alternative_load(a_new, ALTDB_LAX_PARSER)) {
2327 alternative_free(a_new);
2328 continue;
2330 alternative_map_add(alt_map_links, a_new->master_link, a_new);
2331 alternative_map_add(alt_map_parent, a_new->master_name, a_new);
2332 for (sl = a_new->slaves; sl; sl = sl->next) {
2333 alternative_map_add(alt_map_links, sl->link, a_new);
2334 alternative_map_add(alt_map_parent, sl->name, a_new);
2337 alternative_unref(a_new);
2339 altdb_free_namelist(table, count);
2342 static void
2343 alternative_map_free(struct alternative_map *am)
2345 struct alternative_map *am_next;
2347 while (am) {
2348 am_next = am->next;
2349 if (am->item)
2350 alternative_free(am->item);
2351 free(am);
2352 am = am_next;
2356 static char *
2357 alternative_set_manual(struct alternative *a, const char *path)
2359 char *new_choice = NULL;
2361 if (alternative_has_choice(a, path))
2362 new_choice = xstrdup(path);
2363 else
2364 error(_("alternative %s for %s not registered; "
2365 "not setting"), path, a->master_name);
2366 alternative_set_status(a, ALT_ST_MANUAL);
2368 return new_choice;
2371 static char *
2372 alternative_set_auto(struct alternative *a)
2374 char *new_choice = NULL;
2376 alternative_set_status(a, ALT_ST_AUTO);
2377 if (alternative_choices_count(a) == 0)
2378 info(_("there is no program which provides %s"),
2379 a->master_name);
2380 else
2381 new_choice = xstrdup(alternative_get_best(a)->master_file);
2383 return new_choice;
2386 static const char *
2387 get_argv_string(int argc, char **argv)
2389 static char string[2048];
2390 size_t cur_len;
2391 int i;
2393 string[0] = '\0';
2394 cur_len = 0;
2395 for (i = 1; i < argc; i++) {
2396 size_t arg_len = strlen(argv[i]);
2398 if (cur_len + arg_len + 2 > sizeof(string))
2399 break;
2400 if (cur_len) {
2401 strcpy(string + cur_len, " ");
2402 cur_len++;
2404 strcpy(string + cur_len, argv[i]);
2405 cur_len += arg_len;
2408 return string;
2411 static void
2412 alternative_select_mode(struct alternative *a, const char *current_choice)
2414 if (current_choice) {
2415 /* Detect manually modified alternative, switch to manual. */
2416 if (!alternative_has_choice(a, current_choice)) {
2417 if (fsys_pathname_is_missing(current_choice)) {
2418 warning(_("%s%s/%s is dangling; it will be updated "
2419 "with best choice"), instdir, altdir,
2420 a->master_name);
2421 alternative_set_status(a, ALT_ST_AUTO);
2422 } else if (a->status != ALT_ST_MANUAL) {
2423 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 alternative_set_status(a, ALT_ST_MANUAL);
2430 } else {
2431 /* Lack of alternative link => automatic mode. */
2432 verbose(_("setting up automatic selection of %s"),
2433 a->master_name);
2434 alternative_set_status(a, ALT_ST_AUTO);
2438 static void
2439 alternative_evolve_slave(struct alternative *a, const char *cur_choice,
2440 struct slave_link *sl, struct fileset *fs)
2442 struct slave_link *sl_old;
2443 char *new_file = NULL;
2444 const char *old, *new;
2446 sl_old = alternative_get_slave(a, sl->name);
2447 if (sl_old == NULL) {
2448 sl->updated = true;
2449 return;
2452 old = sl_old->link;
2453 new = sl->link;
2455 if (cur_choice && strcmp(cur_choice, fs->master_file) == 0) {
2456 new_file = xstrdup(fileset_get_slave(fs, sl->name));
2457 } else {
2458 char *lnk;
2460 lnk = xasprintf("%s/%s", altdir, sl->name);
2461 new_file = fsys_areadlink(lnk);
2462 free(lnk);
2464 if (strcmp(old, new) != 0 &&
2465 alternative_path_classify(old) == ALT_PATH_SYMLINK) {
2466 bool rename_link = false;
2468 if (new_file)
2469 rename_link = !fsys_pathname_is_missing(new_file);
2471 if (rename_link) {
2472 info(_("renaming %s slave link from %s%s to %s%s"),
2473 sl->name, instdir, old, instdir, new);
2474 fsys_mv(old, new);
2475 } else {
2476 fsys_rm(old);
2479 sl->updated = true;
2481 free(new_file);
2484 static void
2485 alternative_evolve(struct alternative *a, struct alternative *b,
2486 const char *cur_choice, struct fileset *fs)
2488 struct slave_link *sl;
2489 bool is_link;
2491 is_link = alternative_path_classify(a->master_link) == ALT_PATH_SYMLINK;
2492 if (is_link && strcmp(a->master_link, b->master_link) != 0) {
2493 info(_("renaming %s link from %s%s to %s%s"), b->master_name,
2494 instdir, a->master_link, instdir, b->master_link);
2495 fsys_mv(a->master_link, b->master_link);
2497 alternative_set_link(a, b->master_link);
2499 /* Check if new slaves have been added, or existing
2500 * ones renamed. */
2501 for (sl = b->slaves; sl; sl = sl->next) {
2502 alternative_evolve_slave(a, cur_choice, sl, fs);
2503 alternative_copy_slave(a, sl);
2507 static char *
2508 alternative_install(struct alternative **aptr, struct alternative *inst_alt,
2509 const char *current_choice, struct fileset *fileset)
2511 struct alternative *a = *aptr;
2512 char *new_choice = NULL;
2514 if (a->master_link) {
2515 /* Alternative already exists, check if anything got
2516 * updated. */
2517 alternative_evolve(a, inst_alt, current_choice, fileset);
2518 alternative_free(inst_alt);
2519 } else {
2520 /* Alternative doesn't exist, create from parameters. */
2521 alternative_free(a);
2522 *aptr = a = inst_alt;
2524 alternative_add_choice(a, fileset);
2525 if (a->status == ALT_ST_AUTO) {
2526 new_choice = xstrdup(alternative_get_best(a)->master_file);
2527 } else {
2528 verbose(_("automatic updates of %s/%s are disabled; "
2529 "leaving it alone"), altdir, a->master_name);
2530 verbose(_("to return to automatic updates use "
2531 "'%s --auto %s'"), PROGNAME, a->master_name);
2533 return new_choice;
2536 static void
2537 alternative_update(struct alternative *a,
2538 const char *current_choice, const char *new_choice)
2540 enum alternative_update_reason reason;
2542 /* No choice left, remove everything. */
2543 if (!alternative_choices_count(a)) {
2544 log_msg("link group %s fully removed", a->master_name);
2545 alternative_remove_files(a);
2546 return;
2549 /* New choice wanted. */
2550 if (new_choice &&
2551 (!current_choice || strcmp(new_choice, current_choice) != 0)) {
2552 log_msg("link group %s updated to point to %s", a->master_name,
2553 new_choice);
2554 if (a->status == ALT_ST_AUTO)
2555 info(_("using %s to provide %s (%s) in auto mode"),
2556 new_choice, a->master_link, a->master_name);
2557 else
2558 info(_("using %s to provide %s (%s) in manual mode"),
2559 new_choice, a->master_link, a->master_name);
2560 debug("prepare_install(%s)", new_choice);
2561 alternative_prepare_install(a, new_choice);
2562 } else if ((reason = alternative_needs_update(a))) {
2563 if (reason == ALT_UPDATE_SLAVE_CHANGED) {
2564 log_msg("link group %s updated with changed slaves",
2565 a->master_name);
2566 info(_("updating alternative %s "
2567 "because link group %s has changed slave links"),
2568 current_choice, a->master_name);
2569 } else {
2570 log_msg("auto-repair link group %s", a->master_name);
2571 warning(_("forcing reinstallation of alternative %s "
2572 "because link group %s is broken"),
2573 current_choice, a->master_name);
2576 if (current_choice && !alternative_has_choice(a, current_choice)) {
2577 struct fileset *best = alternative_get_best(a);
2579 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 current_choice = best->master_file;
2584 alternative_set_status(a, ALT_ST_AUTO);
2587 if (current_choice)
2588 alternative_prepare_install(a, current_choice);
2591 /* Save administrative file if needed. */
2592 if (a->modified) {
2593 debug("%s is modified and will be saved", a->master_name);
2594 alternative_save(a);
2597 /* Replace all symlinks in one pass. */
2598 alternative_commit(a);
2601 static void
2602 alternative_config_all(void)
2604 struct alternative_map *alt_map_obj;
2605 struct alternative_map *am;
2607 alt_map_obj = alternative_map_new(NULL, NULL);
2608 alternative_map_load_names(alt_map_obj);
2610 for (am = alt_map_obj; am && am->item; am = am->next) {
2611 const char *current_choice;
2612 char *new_choice;
2614 current_choice = alternative_get_current(am->item);
2615 alternative_select_mode(am->item, current_choice);
2617 new_choice = alternative_config(am->item, current_choice);
2619 alternative_update(am->item, current_choice, new_choice);
2621 free(new_choice);
2624 alternative_map_free(alt_map_obj);
2627 static void
2628 alternative_get_selections(void)
2630 struct alternative_map *alt_map_obj;
2631 struct alternative_map *am;
2633 alt_map_obj = alternative_map_new(NULL, NULL);
2634 alternative_map_load_names(alt_map_obj);
2636 for (am = alt_map_obj; am && am->item; am = am->next) {
2637 const char *current;
2639 current = alternative_get_current(am->item);
2640 printf("%-30s %-8s %s\n", am->key,
2641 alternative_status_string(am->item->status),
2642 current ? current : "");
2645 alternative_map_free(alt_map_obj);
2648 static void
2649 alternative_set_selection(struct alternative_map *all, const char *name,
2650 const char *status, const char *choice)
2652 struct alternative *a;
2654 debug("set_selection(%s, %s, %s)", name, status, choice);
2655 a = alternative_map_find(all, name);
2656 if (a) {
2657 char *new_choice = NULL;
2659 if (strcmp(status, "auto") == 0) {
2660 info(_("selecting alternative %s as auto"), name);
2661 new_choice = alternative_set_auto(a);
2662 } else if (alternative_has_choice(a, choice)) {
2663 info(_("selecting alternative %s as choice %s"), name,
2664 choice);
2665 new_choice = alternative_set_manual(a, choice);
2666 } else {
2667 info(_("alternative %s unchanged because choice "
2668 "%s is not available"), name, choice);
2671 if (new_choice) {
2672 const char *current_choice;
2674 current_choice = alternative_get_current(a);
2675 alternative_select_mode(a, current_choice);
2677 alternative_update(a, current_choice, new_choice);
2679 free(new_choice);
2681 } else {
2682 info(_("skip unknown alternative %s"), name);
2686 static void
2687 alternative_set_selections(FILE *input, const char *desc)
2689 struct alternative_map *alt_map_obj;
2691 alt_map_obj = alternative_map_new(NULL, NULL);
2692 alternative_map_load_names(alt_map_obj);
2694 for (;;) {
2695 char line[1024], *res, *name, *status, *choice;
2696 size_t len, i;
2698 errno = 0;
2699 /* Can't use scanf("%s %s %s") because choice can
2700 * contain a space */
2701 res = fgets(line, sizeof(line), input);
2702 if (res == NULL && errno) {
2703 syserr(_("read error in %.250s"), desc);
2704 } else if (res == NULL) {
2705 break;
2707 len = strlen(line);
2708 if (len == 0 || line[len - 1] != '\n') {
2709 error(_("line too long or not terminated while "
2710 "trying to read %s"), desc);
2712 line[len - 1] = '\0';
2713 len--;
2715 /* Delimit name string in line */
2716 i = 0;
2717 name = line;
2718 while (i < len && !isblank(line[i]))
2719 i++;
2720 if (i >= len) {
2721 info(_("skip invalid selection line: %s"), line);
2722 continue;
2724 line[i++] = '\0';
2725 while (i < len && isblank(line[i]))
2726 i++;
2728 /* Delimit status string in line */
2729 status = line + i;
2730 while (i < len && !isblank(line[i]))
2731 i++;
2732 if (i >= len) {
2733 info(_("skip invalid selection line: %s"), line);
2734 continue;
2736 line[i++] = '\0';
2737 while (i < len && isblank(line[i]))
2738 i++;
2740 /* Delimit choice string in the line */
2741 if (i >= len) {
2742 info(_("skip invalid selection line: %s"), line);
2743 continue;
2745 choice = line + i;
2747 alternative_set_selection(alt_map_obj, name, status, choice);
2750 alternative_map_free(alt_map_obj);
2753 static void
2754 alternative_check_name(const char *name)
2756 if (strpbrk(name, "/ \t"))
2757 error(_("alternative name (%s) must not contain '/' "
2758 "and spaces"), name);
2761 static void
2762 alternative_check_link(const char *linkname)
2764 if (linkname[0] != '/')
2765 error(_("alternative link is not absolute as it should be: %s"),
2766 linkname);
2769 static void
2770 alternative_check_path(const char *file)
2772 if (!file || file[0] != '/')
2773 error(_("alternative path is not absolute as it should be: %s"),
2774 file);
2778 * Check the alternative installation arguments.
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.
2784 static void
2785 alternative_check_install_args(struct alternative *inst_alt,
2786 struct fileset *fileset)
2788 struct alternative_map *alt_map_links, *alt_map_parent;
2789 struct alternative *found;
2790 struct slave_link *sl;
2792 alternative_check_name(inst_alt->master_name);
2793 alternative_check_link(inst_alt->master_link);
2794 alternative_check_path(fileset->master_file);
2796 /* Load information about all alternatives to check for mistakes. */
2797 alt_map_links = alternative_map_new(NULL, NULL);
2798 alt_map_parent = alternative_map_new(NULL, NULL);
2799 alternative_map_load_tree(alt_map_links, alt_map_parent);
2801 found = alternative_map_find(alt_map_parent, inst_alt->master_name);
2802 if (found && strcmp(found->master_name, inst_alt->master_name) != 0) {
2803 error(_("alternative %s can't be master: it is a slave of %s"),
2804 inst_alt->master_name, found->master_name);
2807 found = alternative_map_find(alt_map_links, inst_alt->master_link);
2808 if (found && strcmp(found->master_name, inst_alt->master_name) != 0) {
2809 found = alternative_map_find(alt_map_parent,
2810 found->master_name);
2811 error(_("alternative link %s is already managed by %s"),
2812 inst_alt->master_link, found->master_name);
2815 if (fsys_pathname_is_missing(fileset->master_file))
2816 error(_("alternative path %s%s doesn't exist"),
2817 instdir, fileset->master_file);
2819 for (sl = inst_alt->slaves; sl; sl = sl->next) {
2820 const char *file = fileset_get_slave(fileset, sl->name);
2822 alternative_check_name(sl->name);
2823 alternative_check_link(sl->link);
2824 alternative_check_path(file);
2826 found = alternative_map_find(alt_map_parent, sl->name);
2827 if (found &&
2828 strcmp(found->master_name, inst_alt->master_name) != 0) {
2829 if (strcmp(found->master_name, sl->name) == 0)
2830 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 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);
2840 found = alternative_map_find(alt_map_links, sl->link);
2841 if (found &&
2842 strcmp(found->master_name, inst_alt->master_name) != 0) {
2843 error(_("alternative link %s is already "
2844 "managed by %s"), sl->link,
2845 found->master_name);
2847 if (found) {
2848 struct slave_link *sl2;
2850 for (sl2 = found->slaves; sl2; sl2 = sl2->next)
2851 if (strcmp(sl2->link, sl->link) == 0)
2852 break;
2853 if (sl2 && strcmp(sl2->name, sl->name) != 0)
2854 error(_("alternative link %s is already "
2855 "managed by %s (slave of %s)"),
2856 sl->link, sl2->name,
2857 found->master_name);
2861 alternative_map_free(alt_map_links);
2862 alternative_map_free(alt_map_parent);
2866 * Main program
2869 static void
2870 set_action(enum action new_action)
2872 if (action)
2873 badusage(_("two commands specified: --%s and --%s"),
2874 action_names[action].name, action_names[new_action].name);
2875 action = new_action;
2878 static void
2879 set_action_from_name(const char *new_action)
2881 size_t i;
2883 for (i = 0; i < array_count(action_names); i++) {
2884 if (strcmp(new_action, action_names[i].name) == 0) {
2885 set_action(action_names[i].action);
2886 return;
2890 assert(!"unknown action name");
2893 static const char *
2894 set_rootdir(const char *dir)
2896 instdir = fsys_set_dir(dir);
2897 free(log_file);
2898 log_file = fsys_get_path(LOGDIR "/alternatives.log");
2899 altdir = SYSCONFDIR "/alternatives";
2900 free(admdir);
2901 admdir = fsys_gen_admindir();
2903 return instdir;
2906 static char *
2907 admindir_init(void)
2909 const char *basedir_env;
2911 /* Try to get the admindir from an environment variable, usually set
2912 * by the system package manager. */
2913 basedir_env = getenv(ADMINDIR_ENVVAR);
2914 if (basedir_env)
2915 return xasprintf("%s%s", basedir_env, "/alternatives");
2916 else
2917 return fsys_gen_admindir();
2920 #define MISSING_ARGS(nb) (argc < i + nb + 1)
2923 main(int argc, char **argv)
2925 /* Alternative worked on. */
2926 struct alternative *a = NULL;
2927 /* Alternative to install. */
2928 struct alternative *inst_alt = NULL;
2929 /* Set of files to install in the alternative. */
2930 struct fileset *fileset = NULL;
2931 /* Path of alternative we are offering. */
2932 const char *path = NULL;
2933 const char *current_choice = NULL;
2934 char *new_choice = NULL;
2935 bool modifies_alt = false;
2936 bool modifies_sys = false;
2937 int i = 0;
2939 setlocale(LC_ALL, "");
2940 bindtextdomain(PACKAGE, LOCALEDIR);
2941 textdomain(PACKAGE);
2943 tzset();
2944 umask(022);
2946 instdir = fsys_set_dir(NULL);
2947 admdir = admindir_init();
2948 log_file = fsys_get_path(LOGDIR "/alternatives.log");
2950 if (setvbuf(stdout, NULL, _IONBF, 0))
2951 syserr("setvbuf failed");
2953 prog_path = argv[0];
2955 for (i = 1; i < argc; i++) {
2956 if (strstr(argv[i], "--") != argv[i]) {
2957 error(_("unknown argument '%s'"), argv[i]);
2958 } else if (strcmp("--help", argv[i]) == 0) {
2959 usage();
2960 exit(0);
2961 } else if (strcmp("--version", argv[i]) == 0) {
2962 version();
2963 exit(0);
2964 } else if (strcmp("--quiet", argv[i]) == 0) {
2965 opt_verbose = OUTPUT_QUIET;
2966 } else if (strcmp("--verbose", argv[i]) == 0) {
2967 opt_verbose = OUTPUT_VERBOSE;
2968 } else if (strcmp("--debug", argv[i]) == 0) {
2969 opt_verbose = OUTPUT_DEBUG;
2970 } else if (strcmp("--install", argv[i]) == 0) {
2971 const char *alink, *aname, *apath;
2972 char *prio_str, *prio_end;
2973 long prio;
2975 set_action(ACTION_INSTALL);
2976 if (MISSING_ARGS(4))
2977 badusage(_("--%s needs <link> <name> <path> "
2978 "<priority>"), argv[i] + 2);
2980 alink = argv[i + 1];
2981 aname = argv[i + 2];
2982 apath = argv[i + 3];
2983 prio_str = argv[i + 4];
2985 if (strcmp(alink, apath) == 0)
2986 badusage(_("<link> '%s' is the same as <path>"),
2987 alink);
2988 errno = 0;
2989 prio = strtol(prio_str, &prio_end, 10);
2990 if (prio_str == prio_end || *prio_end != '\0')
2991 badusage(_("priority '%s' must be an integer"),
2992 prio_str);
2993 if (prio < INT_MIN || prio > INT_MAX || errno == ERANGE)
2994 badusage(_("priority '%s' is out of range"),
2995 prio_str);
2997 a = alternative_new(aname);
2998 inst_alt = alternative_new(aname);
2999 alternative_set_status(inst_alt, ALT_ST_AUTO);
3000 alternative_set_link(inst_alt, alink);
3001 fileset = fileset_new(apath, prio);
3003 i += 4;
3004 } else if (strcmp("--remove", argv[i]) == 0 ||
3005 strcmp("--set", argv[i]) == 0) {
3006 set_action_from_name(argv[i] + 2);
3007 if (MISSING_ARGS(2))
3008 badusage(_("--%s needs <name> <path>"), argv[i] + 2);
3010 a = alternative_new(argv[i + 1]);
3011 path = argv[i + 2];
3013 alternative_check_name(a->master_name);
3014 alternative_check_path(path);
3016 i += 2;
3017 } else if (strcmp("--display", argv[i]) == 0 ||
3018 strcmp("--query", argv[i]) == 0 ||
3019 strcmp("--auto", argv[i]) == 0 ||
3020 strcmp("--config", argv[i]) == 0 ||
3021 strcmp("--list", argv[i]) == 0 ||
3022 strcmp("--remove-all", argv[i]) == 0) {
3023 set_action_from_name(argv[i] + 2);
3024 if (MISSING_ARGS(1))
3025 badusage(_("--%s needs <name>"), argv[i] + 2);
3026 a = alternative_new(argv[i + 1]);
3028 alternative_check_name(a->master_name);
3030 i++;
3031 } else if (strcmp("--all", argv[i]) == 0 ||
3032 strcmp("--get-selections", argv[i]) == 0 ||
3033 strcmp("--set-selections", argv[i]) == 0) {
3034 set_action_from_name(argv[i] + 2);
3035 } else if (strcmp("--slave", argv[i]) == 0) {
3036 const char *slink, *sname, *spath;
3037 struct slave_link *sl;
3039 if (action != ACTION_INSTALL)
3040 badusage(_("--%s only allowed with --%s"),
3041 argv[i] + 2, "install");
3042 if (MISSING_ARGS(3))
3043 badusage(_("--%s needs <link> <name> <path>"),
3044 argv[i] + 2);
3046 slink = argv[i + 1];
3047 sname = argv[i + 2];
3048 spath = argv[i + 3];
3050 if (strcmp(slink, spath) == 0)
3051 badusage(_("<link> '%s' is the same as <path>"),
3052 slink);
3053 if (strcmp(inst_alt->master_name, sname) == 0)
3054 badusage(_("<name> '%s' is both primary and slave"),
3055 sname);
3056 if (strcmp(slink, inst_alt->master_link) == 0)
3057 badusage(_("<link> '%s' is both primary and slave"),
3058 slink);
3059 if (alternative_has_slave(inst_alt, sname))
3060 badusage(_("duplicate slave <name> '%s'"), sname);
3062 for (sl = inst_alt->slaves; sl; sl = sl->next) {
3063 const char *linkname = sl->link;
3064 if (linkname == NULL)
3065 linkname = "";
3066 if (strcmp(linkname, slink) == 0)
3067 badusage(_("duplicate slave <link> '%s'"),
3068 slink);
3071 alternative_add_slave(inst_alt, sname, slink);
3072 fileset_add_slave(fileset, sname, spath);
3074 i+= 3;
3075 } else if (strcmp("--log", argv[i]) == 0) {
3076 if (MISSING_ARGS(1))
3077 badusage(_("--%s needs a <file> argument"),
3078 argv[i] + 2);
3079 free(log_file);
3080 log_file = fsys_get_path(argv[i + 1]);
3081 i++;
3082 } else if (strcmp("--altdir", argv[i]) == 0) {
3083 if (MISSING_ARGS(1))
3084 badusage(_("--%s needs a <directory> argument"),
3085 argv[i] + 2);
3086 altdir = argv[i + 1];
3087 i++;
3089 /* If altdir is below instdir, convert it to a relative
3090 * path, as we will prepend instdir as needed. */
3091 if (strncmp(altdir, instdir, instdir_len) == 0)
3092 altdir += instdir_len;
3093 } else if (strcmp("--admindir", argv[i]) == 0) {
3094 if (MISSING_ARGS(1))
3095 badusage(_("--%s needs a <directory> argument"),
3096 argv[i] + 2);
3097 free(admdir);
3098 admdir = xstrdup(argv[i + 1]);
3099 i++;
3100 } else if (strcmp("--instdir", argv[i]) == 0) {
3101 if (MISSING_ARGS(1))
3102 badusage(_("--%s needs a <directory> argument"),
3103 argv[i] + 2);
3104 fsys_set_dir(argv[i + 1]);
3105 i++;
3107 /* If altdir is below instdir, convert it to a relative
3108 * path, as we will prepend instdir as needed. */
3109 if (strncmp(altdir, instdir, instdir_len) == 0)
3110 altdir += instdir_len;
3111 } else if (strcmp("--root", argv[i]) == 0) {
3112 if (MISSING_ARGS(1))
3113 badusage(_("--%s needs a <directory> argument"),
3114 argv[i] + 2);
3115 set_rootdir(argv[i + 1]);
3116 i++;
3117 } else if (strcmp("--skip-auto", argv[i]) == 0) {
3118 opt_skip_auto = 1;
3119 } else if (strcmp("--force", argv[i]) == 0) {
3120 opt_force = 1;
3121 } else {
3122 badusage(_("unknown option '%s'"), argv[i]);
3126 if (action == ACTION_NONE)
3127 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");
3133 debug("root=%s admdir=%s altdir=%s", instdir, admdir, altdir);
3135 /* The following actions might modify the current alternative. */
3136 if (action == ACTION_SET ||
3137 action == ACTION_AUTO ||
3138 action == ACTION_CONFIG ||
3139 action == ACTION_REMOVE ||
3140 action == ACTION_REMOVE_ALL ||
3141 action == ACTION_INSTALL)
3142 modifies_alt = true;
3144 /* The following actions might modify the system somehow. */
3145 if (modifies_alt ||
3146 action == ACTION_CONFIG_ALL ||
3147 action == ACTION_SET_SELECTIONS)
3148 modifies_sys = true;
3150 if (action == ACTION_INSTALL)
3151 alternative_check_install_args(inst_alt, fileset);
3153 if (action == ACTION_DISPLAY ||
3154 action == ACTION_QUERY ||
3155 action == ACTION_LIST ||
3156 action == ACTION_SET ||
3157 action == ACTION_AUTO ||
3158 action == ACTION_CONFIG ||
3159 action == ACTION_REMOVE_ALL) {
3160 /* Load the alternative info, stop on failure. */
3161 if (!alternative_load(a, ALTDB_WARN_PARSER))
3162 error(_("no alternatives for %s"), a->master_name);
3163 } 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 if (!alternative_load(a, ALTDB_WARN_PARSER)) {
3168 verbose(_("no alternatives for %s"), a->master_name);
3169 alternative_free(a);
3170 free(log_file);
3171 free(admdir);
3172 exit(0);
3174 } else if (action == ACTION_INSTALL) {
3175 /* Load the alternative info, ignore failures. */
3176 alternative_load(a, ALTDB_WARN_PARSER);
3179 if (modifies_sys)
3180 log_msg("run with %s", get_argv_string(argc, argv));
3182 if (modifies_alt) {
3183 current_choice = alternative_get_current(a);
3184 alternative_select_mode(a, current_choice);
3187 /* Handle actions. */
3188 if (action == ACTION_CONFIG_ALL) {
3189 alternative_config_all();
3190 } else if (action == ACTION_GET_SELECTIONS) {
3191 alternative_get_selections();
3192 } else if (action == ACTION_SET_SELECTIONS) {
3193 alternative_set_selections(stdin, _("<standard input>"));
3194 } else if (action == ACTION_DISPLAY) {
3195 alternative_display_user(a);
3196 } else if (action == ACTION_QUERY) {
3197 alternative_display_query(a);
3198 } else if (action == ACTION_LIST) {
3199 alternative_display_list(a);
3200 } else if (action == ACTION_SET) {
3201 new_choice = alternative_set_manual(a, path);
3202 } else if (action == ACTION_AUTO) {
3203 new_choice = alternative_set_auto(a);
3204 } else if (action == ACTION_CONFIG) {
3205 new_choice = alternative_config(a, current_choice);
3206 } else if (action == ACTION_REMOVE) {
3207 new_choice = alternative_remove(a, current_choice, path);
3208 } else if (action == ACTION_REMOVE_ALL) {
3209 alternative_choices_free(a);
3210 } else if (action == ACTION_INSTALL) {
3211 new_choice = alternative_install(&a, inst_alt, current_choice,
3212 fileset);
3215 if (modifies_alt)
3216 alternative_update(a, current_choice, new_choice);
3218 if (a)
3219 alternative_free(a);
3220 free(new_choice);
3221 free(log_file);
3222 free(admdir);
3224 return 0;