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