You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

858 lines
24 KiB

/*
* Debian Installer main menu program.
*
* Copyright 2000,2004 Joey Hess <joeyh@debian.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include "main-menu.h"
#include <cdebconf/debconfclient.h>
#include <stdarg.h>
#include <stdlib.h>
#include <search.h>
#include <stdio.h>
#include <sys/stat.h>
#include <stdlib.h>
21 years ago
#include <string.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
#include <dirent.h>
#include <errno.h>
const int RAISE = 1;
const int LOWER = 0;
di_hash_table *seen_items;
di_hash_table *notinstallables;
int last_successful_item = -1;
/* Save default priority, to be able to return to it when we have to lower it */
int default_priority = 1;
/* Save priority set by main-menu to detect priority changes from the user */
int local_priority = -1;
/* Force display of the menu at the current priority */
int display_menu = 0;
static struct debconfclient *debconf;
static int di_config_package(di_system_package *p,
int (*virtfunc)(di_system_package *));
static void modify_debconf_priority (int raise_or_lower);
static char *debconf_priorities[] =
{
"low",
"medium",
"high",
"critical"
};
#define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0]))
static int debconf_to_pri (char *priority) {
int i;
if (priority)
for (i = 0; (size_t)i < ARRAY_SIZE(debconf_priorities); ++i)
if (0 == strcmp(priority, debconf_priorities[i]))
return i;
return -1;
}
static char *xasprintf (const char *format, ...)
{
va_list args;
char *result;
va_start(args, format);
if (vasprintf(&result, format, args) < 0) {
if (errno == ENOMEM) {
fputs("Out of memory!\n", stderr);
abort();
}
return NULL;
}
return result;
}
/*
* qsort comparison function (sort by menu item values, fall back to
* lexical sort to resolve ties deterministically).
*/
int package_array_compare (const void *v1, const void *v2) {
di_system_package *p1, *p2;
p1 = *(di_system_package **)v1;
p2 = *(di_system_package **)v2;
int r = p1->installer_menu_item - p2->installer_menu_item;
//di_log(DI_LOG_LEVEL_DEBUG, "menu compare: %s (%i) vs %s (%i): %i",
// p1->p.package, p1->installer_menu_item,
// p2->p.package, p2->installer_menu_item, r);
if (r) return r;
return strcmp(p1->p.package, p2->p.package);
}
static void seen_items_key_destroy (void *key)
{
di_rstring *s = key;
di_free(s->string);
di_free(s);
}
static void notinstallables_key_destroy (void *key)
{
di_rstring *s = key;
di_free(s->string);
di_free(s);
}
int isdefault(di_system_package *p) {
int check;
check = di_system_dpkg_package_control_file_exec(&p->p, "menutest", 0, NULL);
if (check <= 0 || p->p.status == di_package_status_unpacked || p->p.status == di_package_status_half_configured)
return true;
return false;
}
/*
* Menu items with a number equal to 99999 are intended only to be shown
* during package selection in anna, but not in the main menu, so mark
* them uninstallable.
14 years ago
* For other packages, run the isinstallable maintainer script and check
* its return code.
*/
bool isinstallable(di_system_package *p) {
int check;
if (p->installer_menu_item == 99999)
return false;
check = di_system_dpkg_package_control_file_exec(&p->p, "isinstallable", 0, NULL);
if (check <= 0)
return true;
/* Add to table listing not installable packages */
di_rstring *p_name = di_new0(di_rstring, 1);
p_name->string = di_stradup(p->p.key.string, p->p.key.size);
p_name->size = p->p.key.size;
di_hash_table_insert(notinstallables, p_name, p_name);
return false;
}
/* Return nonzero if all of the virtual packages that P provides are
provided by installed packages. */
int provides_installed_virtual_packages(di_package *p) {
di_slist_node *node1, *node2;
int provides = 0;
for (node1 = p->depends.head; node1; node1 = node1->next) {
di_package_dependency *d = node1->data;
if (d->type == di_package_dependency_type_provides) {
int installed = 0;
provides = 1;
for (node2 = d->ptr->depends.head; node2; node2 = node2->next) {
d = node2->data;
if (d->type == di_package_dependency_type_reverse_provides
&& d->ptr->status == di_package_status_installed) {
installed = 1;
break;
}
}
if (!installed)
return 0;
}
}
return provides;
}
/* Expects a topologically ordered linked list of packages. */
static di_system_package *
get_default_menu_item(di_slist *list)
{
di_system_package *p;
di_slist_node *node;
/* Create table listing not installable packages from scratch as
* the isinstallable status can change at any time
*/
di_hash_table_destroy(notinstallables);
notinstallables = di_hash_table_new_full(di_rstring_hash, di_rstring_equal,
notinstallables_key_destroy, NULL);
/* Traverse the list, return the first menu item that isn't installed */
for (node = list->head; node != NULL; node = node->next) {
p = node->data;
//di_log(DI_LOG_LEVEL_DEBUG, "find default; on: %s", p->p.package);
if (!p->installer_menu_item ||
p->p.status == di_package_status_installed ||
!isinstallable(p)) {
//di_log(DI_LOG_LEVEL_DEBUG, "not a menu item, not installed or not installable");
continue;
}
if (p->installer_menu_item >= NEVERDEFAULT) {
//di_log(DI_LOG_LEVEL_DEBUG, "not in range to be default");
continue;
}
if (p->installer_menu_item < last_successful_item &&
di_hash_table_lookup(seen_items, &p->p.key)) {
//di_log(DI_LOG_LEVEL_DEBUG, "menu item (%d) is before last_successful_item (%d), and is not new", p->installer_menu_item, last_successful_item);
continue;
}
/* If menutest says this item should be default, make it so */
if (!isdefault(p)) {
16 years ago
//di_log(DI_LOG_LEVEL_DEBUG, "isdefault says no");
continue;
}
/* If all of the virtual packages provided by a
package have already been satisfied, do not default
to it. */
if (!provides_installed_virtual_packages(&p->p)) {
//di_log(DI_LOG_LEVEL_DEBUG, "success on this one");
return p;
}
//di_log(DI_LOG_LEVEL_DEBUG, "not default");
}
/* Severely broken, there are no menu items in the sorted list */
return NULL;
}
#define menu_entry_maxlen 256
/* Returns a the text of the menu entry for PACKAGE (in a buffer
* that will persist until the next call of the function) */
const char *menu_entry(struct debconfclient *debconf, di_system_package *package)
{
char question[menu_entry_maxlen];
static char buf[menu_entry_maxlen];
snprintf(question, sizeof(question), "debian-installer/%s/title", package->p.package);
if (!debconf_metaget(debconf, question, "Description")) {
strncpy(buf, debconf->value, menu_entry_maxlen);
return buf;
}
/* The following fallback case can go away once all packages
have transitioned to the new form. */
di_log(DI_LOG_LEVEL_INFO, "Falling back to the package description for %s", package->p.package);
if (package->p.short_description)
strncpy(buf, package->p.short_description, menu_entry_maxlen);
else
buf[0]='\0';
return buf;
}
/* Escapes a string so it can be added to a Choices list. */
const char *choices_escape(const char *string) {
static char buf[menu_entry_maxlen * 2];
const char *s;
char *p;
s = strchr(string, ',');
if (s == NULL)
return string;
/* This is not the cheapest way to do it, but a comma in a
* choice is very rare. */
s=string;
p=buf;
while (s[0] != '\0') {
if (s[0] == ',') {
p[0] = '\\';
p++;
}
p[0]=s[0];
p++;
s++;
}
p[0] = '\0';
return buf;
}
/* Priority at which the menu should be displayed */
static int menu_priority() {
static int default_menu_priority = -1;
int menu_prio = -1;
if (default_menu_priority == -1)
default_menu_priority = debconf_to_pri(MENU_PRIORITY);
if (display_menu)
menu_prio = local_priority;
if (menu_prio < 0 ||
(size_t)menu_prio >= ARRAY_SIZE(debconf_priorities))
menu_prio = default_menu_priority;
//di_log(DI_LOG_LEVEL_INFO, "default: %i; debconf: %i; menu: %i",
// default_menu_priority, local_priority, menu_prio);
return menu_prio;
}
/* Displays the main menu via debconf and returns the selected menu item. */
di_system_package *show_main_menu(di_packages *packages, di_packages_allocator *allocator) {
di_system_package **package_array, *p;
di_slist *list;
di_slist_node *node;
di_system_package *menudefault = NULL, *ret = NULL;
int i = 0, num = 0;
char *menu, *s;
const char *buf;
int menu_prio, menu_size, menu_used, size;
for (node = packages->list.head; node; node = node->next) {
p = node->data;
if (((di_system_package *)p)->installer_menu_item)
num++;
}
package_array = di_new (di_system_package *, num + 1);
package_array[num] = NULL;
for (node = packages->list.head; node; node = node->next) {
p = node->data;
if (p->installer_menu_item)
package_array[i++] = node->data;
}
/* Sort by menu number. */
qsort(package_array, num, sizeof (di_system_package *), package_array_compare);
/* Order menu so depended-upon packages come first. */
/* The menu number is really only used to break ties. */
list = di_system_packages_resolve_dependencies_array_permissive (packages, (di_package **) package_array, allocator);
/*
* Generate list of menu choices for debconf.
*/
menu_size = 1024;
menu = di_malloc(menu_size);
menu[0] = '\0';
menu_used = 1;
for (node = list->head; node != NULL; node = node->next) {
p = node->data;
if (!p->installer_menu_item ||
!isinstallable(p))
continue;
buf = choices_escape(menu_entry(debconf, p));
size = strlen(buf);
if (menu_used + size + 2 > menu_size)
{
menu_size += 1024;
menu = di_realloc(menu, menu_size);
}
if (*menu)
strcat(menu, ", ");
strcat(menu, buf);
menu_used += size + 2;
}
menudefault = get_default_menu_item(list);
di_slist_free(list);
21 years ago
/* Make debconf show the menu and get the user's choice. */
debconf_settitle(debconf, "debian-installer/main-menu-title");
debconf_capb(debconf);
debconf_subst(debconf, MAIN_MENU, "MENU", menu);
if (menudefault) {
buf=menu_entry(debconf, menudefault);
debconf_set(debconf, MAIN_MENU, buf);
}
else {
di_log(DI_LOG_LEVEL_INFO, "no default menu item");
modify_debconf_priority(LOWER);
}
menu_prio = menu_priority();
debconf_input(debconf, debconf_priorities[menu_prio], MAIN_MENU);
debconf_go(debconf);
debconf_get(debconf, MAIN_MENU);
s = strdup(debconf->value);
/* Figure out which menu item was selected. */
for (i = 0; i < num; i++) {
p = package_array[i];
buf = menu_entry(debconf, p);
if (strcmp(buf, s) == 0) {
ret = p;
break;
21 years ago
}
}
if (! ret) {
/* This could happen because of a debconf protocol problem
* (for example, leading whitespace in menu items can
* be stripped and confuse the comparisons), or other
* problem. */
di_log(DI_LOG_LEVEL_WARNING, "Internal error! Cannot find \"%s\" in menu.", s);
}
free(s);
free(menu);
free(package_array);
return ret;
23 years ago
}
/*
* Satisfy the dependencies of a virtual package. Its dependencies
* that actually provide the package are presented in a debconf select
* question for the user to pick and choose. Other dependencies are
* just fed recursively through di_config_package.
*/
static int satisfy_virtual(di_system_package *p) {
di_slist_node *node;
di_system_package *dep, *defpkg = NULL;
char *menu, *s = NULL;
const char *buf;
size_t menu_size, menu_used, size;
int is_menu_item = 0;
menu = di_malloc(1024);
menu[0] = '\0';
menu_size = 1024;
menu_used = 1;
/* Compile a list of providing package. The default choice will be the
* package with highest priority. If we have ties, menu items are
* preferred. If we still have ties, the default choice is arbitrary */
for (node = p->p.depends.head; node; node = node->next) {
di_package_dependency *d = node->data;
dep = (di_system_package *)d->ptr;
if (d->type == di_package_dependency_type_depends) {
/* Non-providing dependency */
di_log(DI_LOG_LEVEL_DEBUG, "non-providing dependency from %s to %s", p->p.package, dep->p.package);
if (dep->p.status != di_package_status_installed) {
switch (di_config_package(dep, satisfy_virtual)) {
case -1:
di_free(menu);
return -1;
case EXIT_BACKUP:
di_free(menu);
return EXIT_BACKUP;
}
}
continue;
}
if (d->type != di_package_dependency_type_reverse_provides)
continue;
if (dep->p.status == di_package_status_installed) {
/* This means that a providing package is already
* configure. So we short-circuit. */
menu_used = 0;
break;
}
if (defpkg == NULL || dep->p.priority > defpkg->p.priority ||
(dep->p.priority == defpkg->p.priority &&
dep->installer_menu_item < defpkg->installer_menu_item))
defpkg = dep;
/* This only makes sense if one of the dependencies
* is a menu item */
if (dep->installer_menu_item)
is_menu_item = 1;
buf = choices_escape(menu_entry(debconf, dep));
size = strlen(buf);
if (menu_used + size + 2 > menu_size)
{
menu_size += 1024;
menu = di_realloc(menu, menu_size);
}
if (dep == defpkg) {
/* We want the default to be the first item */
char *temp;
temp = di_malloc (menu_size);
strcpy(temp, menu);
strcpy(menu, buf);
if (strlen(temp)) {
strcat(menu, ", ");
strcat(menu, temp);
}
di_free(temp);
} else {
if (strlen(menu)) {
strcat(menu, ", ");
}
strcat(menu, buf);
}
menu_used += size + 2;
}
if (menu_used >= 2)
menu[menu_used-2] = '\0';
if (menu_used > 1) {
if (is_menu_item) {
char *priority = "medium";
/* Only let the user choose if one of them is a menu item */
if (defpkg != NULL) {
buf = menu_entry(debconf, defpkg);
debconf_set(debconf, MISSING_PROVIDE, buf);
} else {
/* TODO: How to figure out a default? */
priority = "critical";
}
debconf_capb(debconf, "backup");
debconf_subst(debconf, MISSING_PROVIDE, "CHOICES", menu);
debconf_input(debconf, priority, MISSING_PROVIDE);
if (debconf_go(debconf) != 0) {
di_free(menu);
return 0;
}
debconf_capb(debconf);
debconf_get(debconf, MISSING_PROVIDE);
s = strdup(debconf->value);
}
/* Go through the dependencies again */
for (node = p->p.depends.head; node; node = node->next) {
di_package_dependency *d = node->data;
dep = (di_system_package *)d->ptr;
buf = menu_entry(debconf, dep);
if (!is_menu_item || strcmp(s, buf) == 0) {
/* Ick. If we have a menu item it has to match the
* debconf choice, otherwise we configure all of
* the providing packages */
switch (di_config_package(dep, satisfy_virtual)) {
case -1:
di_free(menu);
free(s);
return -1;
case EXIT_BACKUP:
di_free(menu);
free(s);
return EXIT_BACKUP;
}
if (is_menu_item)
break;
}
}
}
di_free(menu);
free(s);
/* It doesn't make sense to configure virtual packages,
* since they are, well, virtual. */
p->p.status = di_package_status_installed;
return 1;
}
static void set_package_title(di_system_package *p) {
char *title;
if (!p->installer_menu_item)
return;
title = xasprintf("debian-installer/%s/title", p->p.package);
if (debconf_settitle(debconf, title))
di_log(DI_LOG_LEVEL_WARNING, "Unable to set title for %s.", p->p.package);
free(title);
}
static int do_menu_item(di_system_package *p) {
di_log(DI_LOG_LEVEL_INFO, "Menu item '%s' selected", p->p.package);
return di_config_package(p, satisfy_virtual);
}
static void modify_debconf_priority (int raise_or_lower) {
int pri;
20 years ago
const char *template = "debconf/priority";
debconf_get(debconf, template);
pri = debconf_to_pri(debconf->value);
if ( pri == -1 )
pri = 1;
if (raise_or_lower == LOWER) {
--pri;
/* Make sure the menu is always displayed after an error */
display_menu = 1;
}
else if (raise_or_lower == RAISE)
++pri;
if (0 > pri)
pri = 0;
if (pri > default_priority)
pri = default_priority;
if (local_priority != pri) {
if (local_priority > -1)
di_log(DI_LOG_LEVEL_INFO, "Modifying debconf priority limit from '%s' to '%s'",
debconf->value ? debconf->value : "(null)",
debconf_priorities[pri] ? debconf_priorities[pri] : "(null)");
local_priority = pri;
debconf_set(debconf, template, debconf_priorities[pri]);
}
}
static void adjust_default_priority (void) {
int pri;
const char *template = "debconf/priority";
debconf_get(debconf, template);
pri = debconf_to_pri(debconf->value);
if (pri > -1 && pri != local_priority) {
if (local_priority > -1)
di_log(DI_LOG_LEVEL_INFO, "Priority changed externally, setting main-menu default to '%s' (%s)",
debconf_priorities[pri] ? debconf_priorities[pri] : "(null)", debconf->value);
local_priority = pri;
default_priority = pri;
}
}
static void restore_default_priority (void) {
const char *template = "debconf/priority";
if (local_priority != default_priority) {
di_log(DI_LOG_LEVEL_INFO, "Restoring default debconf priority '%s'",
debconf_priorities[default_priority] ? debconf_priorities[default_priority] : "(null)");
debconf_set(debconf, template, debconf_priorities[default_priority]);
local_priority = default_priority;
}
}
void notify_user_of_failure (di_system_package *p) {
const char *buf;