/* * Debian Installer main menu program. * * Copyright 2000 Joey Hess * * 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 #include #include #include #include #include #include #include #include #include const int RAISE = 1; const int LOWER = 0; /* 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; static struct debconfclient *debconf; static int di_config_package(di_system_package *p, int (*virtfunc)(di_system_package *), int (*walkfunc)(di_system_package *)); /* * 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; if (r) return r; return strcmp(p1->p.package, p2->p.package); } 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; } bool isinstallable(di_system_package *p) { int check; check = di_system_dpkg_package_control_file_exec(&p->p, "isinstallable", 0, NULL); if (check <= 0) return true; return false; } int provides_installed_virtual_package(di_package *p) { di_slist_node *node1, *node2; for (node1 = p->depends.head; node1; node1 = node1->next) { di_package_dependency *d = node1->data; if (d->type == di_package_dependency_type_provides) 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) return 1; } } return 0; } /* 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; /* Traverse the list, return the first menu item that isn't installed */ for (node = list->head; node != NULL; node = node->next) { p = node->data; if (!p->installer_menu_item || p->p.status == di_package_status_installed || !isinstallable(p)) continue; /* If menutest says this item should be default, make it so */ if (!isdefault(p)) continue; /* Do not default to a package that provides a virtual package that is provided by a package that is already installed. */ if (!provides_installed_virtual_package(&p->p)) return p; } /* Severely broken, there are no menu items in the sorted list */ return NULL; } /* Return the text of the menu entry for PACKAGE, translated to LANGUAGE if possible. */ static size_t menu_entry(struct debconfclient *debconf, char *language, di_system_package *package, char *buf, size_t size) { char question[256]; snprintf(question, sizeof(question), "debian-installer/%s/title", package->p.package); if (language) { /* 256 + strlen("Description-.UTF-8") */ char field[256+18]; char *lang, *end, *und; strcpy(field, "Description-"); lang = language; do { end = strchr(lang, ':'); if (end == NULL) end = lang + strlen(lang); strncpy(field+12, lang, end-lang); strcpy(field+12+(end-lang), ".UTF-8"); if (!debconf_metaget(debconf, question, field)) { strncpy(buf, debconf->value, size); return strlen (buf); } // Was the language of the form xx_YY ? und = strchr(lang, '_'); if (und != NULL && (und - lang) < (end - lang)) { strncpy(field+12, lang, und-lang); strcpy(field+12+(und-lang), ".UTF-8"); if (!debconf_metaget(debconf, question, field)) { strncpy(buf, debconf->value, size); return strlen (buf); } } lang = end+1; } while (*end != '\0'); } 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); } /* 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) { char *language = NULL; 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_size, menu_used, size; debconf_get(debconf,"debian-installer/language"); if (language) free(language); if (debconf->value) language = strdup(debconf->value); 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_packages_resolve_dependencies_array (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, language, p, buf, sizeof (buf)); if (menu_used + size + 2 > menu_size) { menu_size += 1024; menu = di_realloc(menu, menu_size); } strcat(menu, buf); menu_used += size + 2; if (node->next) strcat(menu, ", "); } 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_fset(debconf, MAIN_MENU, "seen", "false"); debconf_subst(debconf, MAIN_MENU, "MENU", menu); if (menudefault) { menu_entry(debconf, language, menudefault, buf, sizeof (buf)); debconf_set(debconf, MAIN_MENU, buf); } debconf_input(debconf, "medium", 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, language, 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_INFO, "Internal error! Cannot find \"%s\" in menu.", s); } free(language); free(package_array); return ret; } static int check_special(di_system_package *p); /* * 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 *language = NULL; char buf[256], *menu, *s = NULL; size_t menu_size, menu_used, size; int is_menu_item = 0; debconf_get(debconf, "debian-installer/language"); if (debconf->value) language = strdup(debconf->value); 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 && !di_config_package(dep, satisfy_virtual, check_special)) return 0; 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, language, 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); strcat(menu, ", "); strcat(menu, temp); di_free(temp); } else { strcat(menu, buf); strcat(menu, ", "); } 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 */ debconf_fset(debconf, MISSING_PROVIDE, "seen", "false"); if (defpkg != NULL) { menu_entry(debconf, language, 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) 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, language, 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 */ if (!di_config_package(dep, satisfy_virtual, check_special)) return 0; if (is_menu_item) break; } } } free(menu); free(s); free(language); /* 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 update_language (void) { debconf_get(debconf, "debian-installer/language"); if (*debconf->value != 0) setenv("LANGUAGE", debconf->value, 1); } static int check_special(di_system_package *p) { di_slist_node *node; /* * A language selection package must provide the virtual * package 'language-selected'. * The LANGUAGE environment variable must be updated */ for (node = p->p.depends.head; node; node = node->next) { di_package_dependency *d = node->data; if (d->type == di_package_dependency_type_provides && strcmp(d->ptr->package, "language-selected") == 0) { update_language(); break; } } return 0; } static void set_package_title(di_system_package *p) { char *title; 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); } int do_menu_item(di_system_package *p) { char *configcommand; const char *showold_template = "debconf/showold"; char *showold = NULL; int ret = 0; di_log(DI_LOG_LEVEL_DEBUG, "Menu item '%s' selected", p->p.package); if (p->p.status == di_package_status_installed) { set_package_title(p); /* The menu item is already configured, so reconfigure it. */ debconf_get(debconf, showold_template); if (debconf->value) showold = strdup(debconf->value); debconf_set(debconf, showold_template, "true"); if (asprintf(&configcommand, "exec udpkg --configure --force-configure %s", p->p.package) == -1) { return 0; } ret = di_exec_shell_log(configcommand); ret = di_exec_mangle_status(ret); free(configcommand); if (showold) { debconf_set(debconf, showold_template, showold); free(showold); } else { debconf_reset(debconf, showold_template); } check_special(p); if (ret) di_log(DI_LOG_LEVEL_WARNING, "Reconfiguring '%s' failed with error code %d", p->p.package, ret); ret = !ret; } else if (p->p.status == di_package_status_unpacked || p->p.status == di_package_status_half_configured) { ret = di_config_package(p, satisfy_virtual, check_special); } return ret; } static char *debconf_priorities[] = { "low", "medium", "high", "critical" }; #define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0])) static void modify_debconf_priority (int raise_or_lower) { int pri, i; const char *template = "debconf/priority"; debconf_get(debconf, template); pri = 1; if ( debconf->value ) { for (i = 0; (size_t)i < ARRAY_SIZE(debconf_priorities); ++i) { if (0 == strcmp(debconf->value, debconf_priorities[i]) ) { pri = i; break; } } } if (raise_or_lower == LOWER) --pri; else if (raise_or_lower == RAISE) ++pri; if (0 > pri) pri = 0; if (pri > default_priority) pri = default_priority; if (local_priority != pri) { 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, i; const char *template = "debconf/priority"; debconf_get(debconf, template); pri = 1; if ( debconf->value ) { for (i = 0; (size_t)i < ARRAY_SIZE(debconf_priorities); ++i) { if (0 == strcmp(debconf->value, debconf_priorities[i]) ) { pri = i; break; } } } if ( pri != local_priority ) { 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; } } int main (int argc __attribute__ ((unused)), char **argv) { di_system_package *p; di_packages *packages; di_packages_allocator *allocator; int ret; debconf = debconfclient_new(); di_system_init(basename(argv[0])); /* Tell udpkg to shut up. */ setenv("UDPKG_QUIET", "y", 1); allocator = di_system_packages_allocator_alloc (); packages = di_system_packages_status_read_file(DI_SYSTEM_DPKG_STATUSFILE, allocator); while ((p=show_main_menu(packages, allocator))) { ret = do_menu_item(p); adjust_default_priority(); if (!ret) { if (ret == 10) di_log(DI_LOG_LEVEL_INFO, "Menu item '%s' succeeded but requested to be left unconfigured.", p->p.package); else di_log(DI_LOG_LEVEL_WARNING, "Menu item '%s' failed.", p->p.package); /* Something went wrong. Lower debconf priority limit to try to give the user more control over the situation. */ modify_debconf_priority(LOWER); } else modify_debconf_priority(RAISE); 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 */ kill(getppid(), SIGUSR1); } return(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 *), int (*walkfunc)(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\n", p->p.package, p->p.status); if (p->p.type == di_package_type_virtual_package) { di_log(DI_LOG_LEVEL_DEBUG, "virtual package %s\n", p->p.package); if (virtfunc) return virtfunc(p); else return 0; } else if (p->p.type == di_package_type_non_existent) return 1; 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 */ if (!di_config_package(dep, virtfunc, walkfunc)) return 0; } set_package_title(p); if (asprintf(&configcommand, "exec udpkg --configure %s", p->p.package) == -1) { return 0; } ret = di_exec_shell_log(configcommand); ret = di_exec_mangle_status(ret); free(configcommand); if (ret == 0) { p->p.status = di_package_status_installed; if (walkfunc != NULL) walkfunc(p); } else { di_log(DI_LOG_LEVEL_WARNING, "Configuring '%s' failed with error code %d", p->p.package, ret); p->p.status = di_package_status_half_configured; return 0; } return !ret; }