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.
 
 

800 lines
23 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 <stdlib.h>
#include <search.h>
#include <stdio.h>
#include <sys/stat.h>
#include <stdlib.h>
#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;
}
/*
* 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.
* 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 is before last_successful_item, and is not new");
continue;
}
/* If menutest says this item should be default, make it so */
if (!isdefault(p)) {
//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;
}
/* Return the text of the menu entry for PACKAGE. */
static size_t menu_entry(struct debconfclient *debconf, di_system_package *package, char *buf, size_t size)
{
char question[256];
snprintf(question, sizeof(question), "debian-installer/%s/title", package->p.package);
if (!debconf_metaget(debconf, question, "Description")) {
strncpy(buf, debconf->value, size);
return strlen (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, size);
return strlen (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 buf[256], *menu, *s;
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 = di_malloc(1024);
menu[0] = '\0';
menu_size = 1024;
menu_used = 1;
for (node = list->head; node != NULL; node = node->next) {
p = node->data;
if (!p->installer_menu_item ||
!isinstallable(p))
continue;
size = menu_entry(debconf, p, buf, sizeof (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);
/* 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) {
menu_entry(debconf, menudefault, buf, sizeof (buf));
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];
menu_entry(debconf, p, buf, sizeof (buf));
if (strcmp(buf, s) == 0) {
ret = p;
break;
}
}
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;
}
/*
* 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 buf[256], *menu, *s = NULL;
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;
size = menu_entry(debconf, dep, buf, sizeof (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) {
menu_entry(debconf, defpkg, buf, sizeof(buf));
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;
menu_entry(debconf, dep, buf, sizeof(buf));
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;
asprintf(&title, "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;
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) {
char buf[256];
set_package_title(p);
debconf_capb(debconf);
menu_entry(debconf, p, buf, sizeof (buf));
debconf_subst(debconf, ITEM_FAILURE, "ITEM", buf);
debconf_input(debconf, "critical", ITEM_FAILURE);
debconf_go(debconf);
debconf_capb(debconf, "backup");
}
/* Cheap-and-cheerful run-parts-a-like for /lib/main-menu.d. Allows packages
* to register scripts to be run at main-menu startup that need to share
* main-menu's debconf frontend but that don't merit a menu item, such as
* setting an info message.
*/
static void menu_startup (void) {
struct dirent **namelist;
int entries, i;
/* scandir() isn't POSIX, but it makes things easy. */
entries = scandir(MAIN_MENU_DIR, &namelist, NULL, alphasort);
if (entries < 0)
return;
for (i = 0; i < entries; ++i) {
size_t len;
char *filename;
struct stat st;
int ret;
if (strcmp(namelist[i]->d_name, ".") == 0 || strcmp(namelist[i]->d_name, "..") == 0)
continue;
/* sizeof(MAIN_MENU_DIR) includes trailing \0 */
len = sizeof(MAIN_MENU_DIR) + 1 + strlen(namelist[i]->d_name);
filename = di_new(char, len);
snprintf(filename, len, "%s/%s", MAIN_MENU_DIR, namelist[i]->d_name);
if (stat(filename, &st) != 0) {
di_log(DI_LOG_LEVEL_WARNING, "Can't stat %s (%s)", filename, strerror(errno));
di_free(filename);
continue;
}
if (!S_ISREG(st.st_mode)) {
di_log(DI_LOG_LEVEL_WARNING, "%s is not a regular file", filename);
di_free(filename);
continue;
}
if (access(filename, X_OK) != 0) {
di_log(DI_LOG_LEVEL_WARNING, "%s is not executable", filename);
di_free(filename);
continue;
}
//di_log(DI_LOG_LEVEL_DEBUG, "Executing %s", filename);
ret = system(filename);
if (ret != 0)
di_log(DI_LOG_LEVEL_WARNING, "%s exited with status %d", filename, ret);
di_free(filename);
}
}
int main (int argc __attribute__ ((unused)), char **argv) {
di_system_package *p;
di_packages *packages;
di_packages_allocator *allocator;
int ret, exit_loop;
debconf = debconfclient_new();
di_system_init(basename(argv[0]));
/* Tell udpkg to shut up. */
setenv("UDPKG_QUIET", "y", 1);
/* Make cdebconf honour currently set language */
const char *template = "debconf/language";
if (debconf_get(debconf, template) == CMD_SUCCESS &&
debconf->value && *debconf->value)
debconf_set(debconf, template, debconf->value);
/* Initialize internal priority variables */
adjust_default_priority();
menu_startup();
seen_items = di_hash_table_new_full(di_rstring_hash, di_rstring_equal,
seen_items_key_destroy, NULL);
notinstallables = di_hash_table_new_full(di_rstring_hash, di_rstring_equal,
notinstallables_key_destroy, NULL);
exit_loop = 0;
allocator = di_system_packages_allocator_alloc ();
packages = di_system_packages_status_read_file(DI_SYSTEM_DPKG_STATUSFILE, allocator);
while (!exit_loop && (p=show_main_menu(packages, allocator))) {
di_slist_node *node;
if (p->installer_menu_item < NEVERDEFAULT && display_menu) {
display_menu = 0;
}
ret = do_menu_item(p);
adjust_default_priority();
switch (ret) {
case EXIT_OK:
/* Success */
if (p->installer_menu_item < NEVERDEFAULT) {
last_successful_item = p->installer_menu_item;
restore_default_priority();
//di_log(DI_LOG_LEVEL_DEBUG, "Installed package '%s', raising last_successful_item to %d", p->p.package, p->installer_menu_item);
}
else {
// di_log(DI_LOG_LEVEL_DEBUG, "Installed package '%s' but no raise since %d >= %i", p->p.package, p->installer_menu_item, NEVERDEFAULT);
}
break;
case EXIT_BACKUP:
di_log(DI_LOG_LEVEL_INFO, "Menu item '%s' succeeded but requested to be left unconfigured.", p->p.package);
display_menu = 1;
break;
case EXIT_INSTALLER:
/* Interrupt the loop and exit properly */
exit_loop = 1;
break;
default:
di_log(DI_LOG_LEVEL_WARNING, "Menu item '%s' failed.", p->p.package);
notify_user_of_failure(p);
modify_debconf_priority(LOWER);
}
/* Remember all the packages we've seen so far */
for (node = packages->list.head; node; node = node->next) {
di_system_package *seen = node->data;
di_rstring *seen_name = di_new0(di_rstring, 1);
seen_name->string = di_stradup(seen->p.key.string,
seen->p.key.size);
seen_name->size = seen->p.key.size;
if (! di_hash_table_lookup(notinstallables, &seen->p.key))
di_hash_table_insert(seen_items, seen_name, seen_name);
}
di_packages_free (packages);
di_packages_allocator_free (allocator);
allocator = di_system_packages_allocator_alloc ();
packages = di_system_packages_status_read_file(DI_SYSTEM_DPKG_STATUSFILE, allocator);
/* tell cdebconf to save the database */
debconf->command(debconf, "X_SAVE", NULL);
}
return exit_loop != 0 ? EXIT_OK : EXIT_FAILURE;
}
int di_config_package_depth=0;
/*
* Configure all dependencies, special case for virtual packages.
* This is done depth-first.
*/
static int di_config_package(di_system_package *p,
int (*virtfunc)(di_system_package *)) {
char *configcommand;
int ret;
di_slist_node *node;
di_system_package *dep;
//di_log(DI_LOG_LEVEL_DEBUG, "configure %s, status: %d", p->p.package, p->p.status);
if (p->p.type == di_package_type_virtual_package) {
//di_log(DI_LOG_LEVEL_DEBUG, "virtual package %s", p->p.package);
if (virtfunc)
return virtfunc(p);
else
return -1;
}
else if (p->p.type == di_package_type_non_existent)
return 0;
for (node = p->p.depends.head; node; node = node->next) {
di_package_dependency *d = node->data;
dep = (di_system_package *)d->ptr;
if (dep->p.status == di_package_status_installed)
continue;
if (d->type != di_package_dependency_type_depends)
continue;
/* Recursively configure this package */
di_config_package_depth++;
if (di_config_package_depth > 1000) {
di_log(DI_LOG_LEVEL_WARNING, "Deep recursion configuring package %s (dep loop?)", p->p.package);
return -1;
}
ret = di_config_package(dep, virtfunc);
di_config_package_depth--;
switch (ret) {
case -1:
return -1;
case EXIT_BACKUP:
return EXIT_BACKUP;
}
}
set_package_title(p);
if (asprintf(&configcommand, "exec udpkg --configure --force-configure %s", p->p.package) == -1)
return -1;
ret = di_exec_shell_log(configcommand);
ret = di_exec_mangle_status(ret);
free(configcommand);
switch (ret) {
case EXIT_OK:
case EXIT_INSTALLER:
p->p.status = di_package_status_installed;
break;
default:
di_log(DI_LOG_LEVEL_WARNING, "Configuring '%s' failed with error code %d", p->p.package, ret);
ret = -1;
case EXIT_BACKUP:
p->p.status = di_package_status_half_configured;
break;
}
return ret;
}
/* vim: noexpandtab sw=8
*/