/** * Copyright (C) 2002,2003, 2005 Alastair McKinstry, * Released under the GPL */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "nls.h" #include "xmalloc.h" #include "kbd-chooser.h" extern int loadkeys_wrapper (char *map); // in loadkeys.y typedef enum { SERIAL_ABSENT = 0, SERIAL_PRESENT = 1, SERIAL_UNKNOWN = 2 } sercon_state; struct debconfclient * mydebconf_client (void) { static struct debconfclient *client = NULL; if (client == NULL) client = debconfclient_new (); return client; } int mydebconf_ask (char *priority, char *template, char **result) { int res; struct debconfclient *client = mydebconf_client (); debconf_input (client, priority, template); res = debconf_go (client); if (res != CMD_SUCCESS) return res; res = debconf_get (client, template); *result = client->value; return res; } /** * @brief Set a default value for a question if one not already there */ int mydebconf_default_set (char *template, char *value) { int res = 0; struct debconfclient *client = mydebconf_client (); if ((res = debconf_get (client, template))) return res; if (client->value == NULL || (strlen (client->value) == 0)) res = debconf_set (client, template, value); return res; } /** * @brief Ensure a directory is present (and readable) */ int check_dir (const char *dirname) { struct stat buf; if (stat (dirname, &buf)) return 0; return S_ISDIR(buf.st_mode); } /** * @brief Do a grep for a string * @return 0 if not found, -errno if error, * and LINESIZE * (line - 1) + pos, when found at line, pos. */ int grep (const char *file, const char *string) { FILE *fp = fopen (file, "r"); char buf[LINESIZE]; char * ret; int lines = 0; if (!fp) return -errno; while (!feof (fp)) { if (fgets (buf, LINESIZE, fp) == NULL) { if (ferror (fp)) di_error ("fgets failed: %s", strerror (errno)); break; } if ((ret = strstr (buf, string)) != NULL) { fclose (fp); return ((int) (ret - buf) + 1 + (lines * LINESIZE)); } lines ++; } fclose (fp); return 0; } /* * Helper routines for maplist_select */ /** * @brief return a default locale name, eg. en_US.UTF-8 (Change C, POSIX to en_US) * @return - char * locale name (freed by caller) */ char * locale_get (void) { int ret = 0; struct debconfclient *client = mydebconf_client (); // localechooser sets locale of the form xx_YY // NO encoding used. ret = debconf_get (client, "debian-installer/locale"); if ((ret == 0) && client->value && (strlen (client->value) > 0)) return strdup(client->value); else return strdup("en_US"); } /** * @brief parse a locale into pieces. Assume a well-formed locale name * */ void locale_parse (char *locale, char **lang, char **territory, char **modifier, char **charset) { char *und, *at, *dot, *loc = strdup (locale); // Each separator occurs only once in a well-formed locale und = strchr (loc, '_'); at = strchr (loc, '@'); dot = strchr (loc, '.'); if (und) *und = '\0'; if (at) *at = '\0'; if (dot) *dot = '\0'; *lang = loc; *territory = und ? (und + 1) : NULL; *modifier = at ? (at + 1 ) : NULL; *charset = dot ? (dot + 1) : NULL; } /** * @brief compare langs list with the preferred locale * @param langs: colon-seperated list of locales * @return score 0-8 */ int locale_list_compare (char *langs) { static char *locale = NULL, *lang1 = NULL, *territory1 = NULL, *modifier1 = NULL, *charset1 = NULL; char *lang2 = NULL, *territory2 = NULL, *charset2 = NULL, *modifier2 = NULL, buf[LINESIZE], *s, *colon; int score = 0, best = -1; if (!locale) { locale = locale_get (); locale_parse (locale, &lang1, &territory1, &modifier1, &charset1); } strcpy (buf, langs); s = buf; while (s) { colon = strchr (s, ':'); if (colon) *colon = '\0'; locale_parse (s, &lang2, &territory2, &modifier2, &charset2); if (!strcmp (lang1, lang2)) { score = 3; if (territory1 && territory2 && !strcmp (territory1, territory2)) { score+=2; } // Favour 'generic' locales; ie 'fr' matches 'fr' better // than 'fr_BE' does if (!territory1 && territory2) score--; if (territory1 && !territory2) score++; if (charset1 && charset2 && !strcmp (charset1, charset2)) { score += 3; // charset more important than territory } } best = (score > best) ? score : best; s = colon ? (colon + 1) : NULL; } return best; } /** * @brief Insert description into buffer * @description ; may be NULL. * @return ptr to char after description. */ char * insert_description (char *buf, char *description, int *first_entry) { char *s = buf; char *t = description; if (*first_entry) { *first_entry = 0; } else { strcpy (s, ", "); s += 2; } while (*t) { if (*t == '\\' || *t == ',') *s++ = '\\'; *s++ = *t++; } *s = '\0'; return s; } /** * @brief Enter a maplist into debconf, picking a default via locale. * @param maplist - a maplist (for a given arch, for example) */ void maplist_select (maplist_t * maplist) { char template[LINESIZE]; keymap_t *mp, *preferred = NULL; int score = 0, best = -1; // Pick the default for (mp = maplist->maps ; mp != NULL ; mp = mp->next) { score = locale_list_compare (mp->langs); if (score > best) { best = score; preferred = mp; } } if (best > 0) { sprintf (template, "console-keymaps-%s/keymap", maplist->name); mydebconf_default_set (template, preferred->name); } } /** * @brief Get a maplist "name", creating if necessary * @name name of arch, eg. "at", "mac" */ maplist_t *maplist_get (const char *name) { static maplist_t *maplists = NULL; maplist_t *p; for (p = maplists; p != NULL; p = p->next) { if (strcmp (p->name, name) == 0) break; } if (p) return p; p = di_new (maplist_t,1); if (p == NULL) { di_error (": Failed to create maplist (out of memory)\n"); exit (1); } p->next = maplists; p->maps = NULL; p->name = strdup (name); maplists = p; return p; } /** * @brief Get a keymap in a maplist; create if necessary * @list maplist to search * @name name of list * @langs if non-NULL, match these languages */ keymap_t *keymap_get (maplist_t * list, const char *name, const char *langs) { keymap_t *mp; for (mp = list->maps ; mp != NULL; mp = mp->next) { if (strcmp (mp->name, name) == 0 && (!langs || strcmp (mp->langs, langs) == 0)) break; } if (mp) return mp; mp = di_new (keymap_t,1); if (mp == NULL) { di_error (": Failed to malloc keymap_t"); exit (2); } mp->langs = NULL; mp->name = strdup (name); mp->description = NULL; mp->next = list->maps; list->maps = mp; return mp; } /** * @brief Load the keymap files into memory @ @name keymap filename. * @warning No error checking on file contents. Assumed correct by installation checks. */ maplist_t * maplist_parse_file (const char *name) { FILE *fp; maplist_t *maplist; keymap_t *map; char buf[LINESIZE], *tab1, *tab2, *nl; fp = fopen (name, "r"); if (fp == NULL) { di_error (": Failed to open %s: %s \n", name, strerror (errno)); exit (3); } maplist = maplist_get ((char *) (name + strlen (KEYMAPLISTDIR) + strlen ("console-keymaps-") + 1)); while (!feof (fp)) { if (fgets (buf, LINESIZE, fp) == NULL) { if (ferror (fp)) di_error ("fgets failed: %s", strerror (errno)); break; } if (*buf == '#') //comment ; skip line continue; tab1 = strchr (buf, '\t'); if (!tab1) continue; // malformed line tab2 = strchr (tab1 + 1, '\t'); if (!tab2) continue; // malformed line nl = strchr (tab2, '\n'); if (!nl) continue; // malformed line *tab1 = '\0'; *tab2 = '\0'; *nl = '\0'; map = keymap_get (maplist, tab1 + 1, buf); if (!map->langs) { // new keymap map->langs = strdup (buf); map->description = strdup (tab2 + 1); } } fclose (fp); return maplist; } /** * @brief Read keymap files from /usr/share/console/lists and parse them * @listdir Directory to look in * @warning Assumes files present, readable: this should be guaranteed by the installer dependencies */ void read_keymap_files (char *listdir) { DIR *d; char *p, fullname[LINESIZE]; struct dirent *ent; struct stat sbuf; strncpy (fullname, listdir, LINESIZE); p = fullname + strlen (listdir); *p++ = '/'; d = opendir (listdir); if (d == NULL) { di_error (": Failed to open %s: %s (keymap files probably not installed)\n", listdir, strerror (errno)); exit (4); } ent = readdir (d); for (; ent; ent = readdir (d)) { if ((strcmp (ent->d_name, ".") == 0) || (strcmp (ent->d_name, "..") == 0)) continue; strcpy (p, ent->d_name); if (stat (fullname, &sbuf) == -1) { di_error (": Failed to stat %s: %s\n", fullname, strerror (errno)); exit (5); } if (S_ISDIR (sbuf.st_mode)) { read_keymap_files (fullname); } else { // Assume a file /* two types of name allowed (for the moment; ) * legacy 'console-keymaps-* names and *.keymaps names */ if (strncmp (ent->d_name, "console-keymaps-", 16) == 0) { strcpy (p, ent->d_name); maplist_select (maplist_parse_file (fullname)); } } } closedir (d); } /** * @brief Sort keyboards */ void keyboards_sort (kbd_t ** keyboards) { kbd_t *p = *keyboards, **prev; int in_order = 1; // Yes, it's bubblesort. But for this size of list, it's efficient while (!in_order) { in_order = 1; p = *keyboards; prev = keyboards; while (p) { if (p->next && (strcmp (p->next->description, p->description) < 0)) { in_order = 0; *prev = p->next; p->next = p->next->next; (*prev)->next = p; } prev = &(p->next); p = p->next; } } } /** * @brief Get template contents (in the current language). * @param template Template name * @param type Kind of entry * @return the translation, to be freed by caller */ char * template_get(const char *template, const char *type) { struct debconfclient *client = mydebconf_client(); /* cdebconf auto-translates */ debconf_metaget(client, template, type); return strdup(client->value); } /** * @brief Get translated description for a given template. * @param template Template name * @return the translation, to be freed by caller */ char * description_get(const char *template) { return template_get(template, "Description"); } /** * @brief Build a list of the keyboards present on this computer * @returns kbd_t list */ kbd_t * keyboards_get (void) { static kbd_t *keyboards = NULL, *p = NULL; char buf[25]; const char *subarch = di_system_subarch_analyze(); if (keyboards != NULL) return keyboards; #if defined (USB_KBD) keyboards = usb_kbd_get (keyboards, subarch); #endif #if defined (AT_KBD) keyboards = at_kbd_get (keyboards, subarch); #endif #if defined (MAC_KBD) keyboards = mac_kbd_get (keyboards, subarch); #endif #if defined (SPARC_KBD) keyboards = sparc_kbd_get (keyboards, subarch); #endif #if defined (ATARI_KBD) keyboards = atari_kbd_get (keyboards, subarch); #endif #if defined (AMIGA_KBD) keyboards = amiga_kbd_get (keyboards, subarch); #endif #if defined (SERIAL_KBD) keyboards = serial_kbd_get (keyboards, subarch); #endif #if defined (HIL_KBD) keyboards = hil_kbd_get (keyboards, subarch); #endif // Did we forget to compile in a keyboard ??? if (DEBUG && keyboards == NULL) { di_error (": No keyboards found\n"); exit (6); } // Get the (translated) keyboard names for (p = keyboards; p != NULL; p = p->next) { sprintf(buf, "kbd-chooser/kbd/%s", p->name); p->description = description_get(buf); } return keyboards; } /** * @brief set debian-installer/uml-console as to whether we are using a user mode linux console * @return 1 if present, 0 if absent, 2 if unknown. */ sercon_state check_if_uml_console (void) { sercon_state present = SERIAL_UNKNOWN; struct debconfclient *client = mydebconf_client (); if (grep("/proc/cpuinfo", "User Mode Linux") > 0) present = SERIAL_PRESENT; else present = SERIAL_ABSENT; debconf_set (client, "debian-installer/uml-console", present ? "true" : "false"); di_info ("Setting debian-installer/uml-console to %s", present ? "true" : "false"); return present; } /** * @brief set debian-installer/serial console as to whether we are using a serial console * @return 1 if present, 0 if absent, 2 if unknown. */ sercon_state check_if_serial_console (void) { sercon_state present = SERIAL_UNKNOWN; struct debconfclient *client = mydebconf_client (); int fd; struct serial_struct sr; int rets, ret1, ret2, ret; // Some UARTs don't support the TIOCGSERIAL ioctl(), so also // try to detect serial console via cmdline ret1 = grep("/proc/cmdline","console=ttyS"); ret2 = grep("/proc/cmdline","console=ttys"); rets = ret1 > ret2 ? ret1 : ret2; ret = grep("/proc/cmdline","console=tty0"); if ((rets > 0) && (rets > ret)) { present = SERIAL_PRESENT; } else { fd = open ("/dev/console", O_NONBLOCK); if (fd == -1) return SERIAL_UNKNOWN; present = (ioctl (fd, TIOCGSERIAL, &sr) == 0) ? SERIAL_PRESENT : SERIAL_ABSENT; close (fd); } debconf_set (client, "debian-installer/serial-console", present ? "true" : "false"); di_info ("Setting debian-installer/serial-console to %s", present ? "true" : "false"); return present; } /** * @brief Make sure there are no double values in the arch selection dialog. * Because usb-kbd.c adds a keyboard for each keyboard detected and * because usb-kbd.c may add keyboard type 'at' in some situations when an * usb keyboard is detected, it is possible the types 'usb' and 'at' could * be present more than once. * The functions add_kbdtype and kbdtype_present test for this situation. * FIXME: This could maybe be removed when the problems with keymap types for * usb keyboards have been resolved. */ typedef struct kbdtype_s { char *name; // short name of kbd arch struct kbdtype_s *next; } kbdtype_t; static inline kbdtype_t *add_kbdtype (kbdtype_t *archlist, char *name) { kbdtype_t *ka = xmalloc (sizeof(kbdtype_t)); ka->name = name; ka->next = archlist; archlist = ka; return archlist; } int kbdtype_present (kbdtype_t *archlist, char *name) { kbdtype_t *ka = NULL; for (ka = archlist; ka != NULL; ka = ka->next) { if (strcmp (ka->name, name) == 0) return TRUE; } return FALSE; } /** * @brief Pick a keyboard, adding it to debconf. * @return const char * - priority of question */ char * keyboard_select (char *curr_arch) { kbd_t *kp = NULL, *preferred = NULL; kbdtype_t *archlist = NULL; char buf_s[LINESIZE], *s = NULL, buf_t[LINESIZE], *t = NULL, *arch_descr = NULL; int choices = 0, first_entry_s = 1, first_entry_t = 1; sercon_state sercon; sercon_state umlcon; struct debconfclient *client = mydebconf_client (); /* k is returned by a method if it is preferred keyboard. * For 2.4 kernels, we just select one keyboard. * In 2.6+ we may have per-keyboard keymaps, and better autodetection * of keyboards present. */ s = buf_s; t = buf_t; // Add the keyboards to debconf for (kp = keyboards_get (); kp != NULL; kp = kp->next) { di_info ("keyboard type %s: present: %s \n", kp->name, kp->present == UNKNOWN ? "unknown ": (kp->present == TRUE ? "true: " : "false" )); if ((kp->present != FALSE) && (kbdtype_present (archlist, kp->name) == FALSE)) { choices++; s = insert_description (s, kp->name, &first_entry_s); t = insert_description (t, kp->description, &first_entry_t); archlist = add_kbdtype (archlist, kp->name); if (strcmp (PREFERRED_KBD, kp->name) == 0) { if ((preferred == NULL) || (preferred->present == UNKNOWN) || (kp->present == TRUE)) preferred = kp; } else { if ((preferred == NULL) || (preferred->present != TRUE && kp->present == TRUE)) preferred = kp; } } } sercon = check_if_serial_console(); umlcon = check_if_uml_console(); if (sercon == SERIAL_PRESENT || umlcon == SERIAL_PRESENT) { debconf_metaget(client, "kbd-chooser/no-keyboard", "Description"); arch_descr = strdup(client->value); choices++; s = insert_description (s, "no-keyboard", &first_entry_s); t = insert_description (t, arch_descr, &first_entry_t); mydebconf_default_set ("console-tools/archs", "no-keyboard"); } else { // Add option to skip keyboard configuration (use kernel keymap) debconf_metaget(client, "kbd-chooser/skip-config", "Description"); arch_descr = strdup(client->value); choices++; s = insert_description (s, "skip-config", &first_entry_s); t = insert_description (t, arch_descr, &first_entry_t); mydebconf_default_set ("console-tools/archs", preferred ? preferred->name : "skip-config"); } debconf_subst (client, "console-tools/archs", "KBD-ARCHS", buf_s); debconf_subst (client, "console-tools/archs", "KBD-ARCHS-L10N", buf_t); free(arch_descr); // Set medium priority if current selection is no-keyboard or skip-config return ((sercon == SERIAL_PRESENT) || (umlcon == SERIAL_PRESENT) || ((preferred && preferred->present == TRUE) && (strcmp (curr_arch, "skip-config") != 0) && (strcmp (curr_arch, "no-keyboard") != 0))) ? "low" : "medium"; } /** * @brief choose a given keyboard * @arch keyboard architecture * @keymap ptr to buffer in which to store chosen keymap name * @returns CMD_SUCCESS or CMD_GOBACK, keymap set if SUCCESS */ int keymap_select (char *arch, char *keymap) { char template[50], *ptr; kbd_t *kb; int res; sprintf (template, "console-keymaps-%s/keymap", arch); for (kb = keyboards_get (); kb != NULL; kb = kb->next) if (!strcmp (kb->name, arch)) break; if (DEBUG && !kb) { di_error ("Keyboard not found\n"); exit (7); } // If there is a default keymap for this keyboard, select it // This is set if we can actually read preferred type from keyboard, // and shouldn't have to ask the question. if (kb->deflt) { keymap_get (maplist_get (arch), kb->deflt, NULL); mydebconf_default_set (template, kb->deflt); } res = mydebconf_ask ( kb->deflt ? "low" : "high", template, &ptr); if (res != CMD_SUCCESS) return res; strcpy (keymap, (strlen (ptr) == 0) ? "skip-config" : ptr); return CMD_SUCCESS; } /** * @brief set the keymap, and debconf database */ void keymap_set (struct debconfclient *client, char *keymap) { di_info ("kbd_chooser: setting keymap %s", keymap); debconf_set (client, "debian-installer/keymap", keymap); // "seen" Used by scripts to decide not to call us again // NOTE: not a typo, using 'true' makes things fail. amck!!! debconf_fset (client, "debian-installer/keymap", "seen", "yes"); loadkeys_wrapper (keymap); } int main (int argc, char **argv) { char *kbd_priority, buf[LINESIZE], *arch; char keymap[LINESIZE]="no keymap"; enum { GOBACK, CHOOSE_ARCH, CHOOSE_KEYMAP, QUIT } state = CHOOSE_ARCH; struct debconfclient *client; setlocale (LC_ALL, ""); client = mydebconf_client (); di_system_init("kbd-chooser"); // enable syslog if (argc == 2) { // keymap may be specified on command-line keymap_set (client, argv[1]); exit (0); } debconf_capb (client, "backup"); debconf_version (client, 2); read_keymap_files (KEYMAPLISTDIR); arch = buf; if (debconf_get (client, "console-tools/archs") == CMD_SUCCESS) arch = strdup(client->value); kbd_priority = keyboard_select (arch); while (state != QUIT) { switch (state) { case GOBACK: di_info ("kbdchooser: GOBACK received; leaving"); exit (10); case CHOOSE_ARCH: // First select a keyboard arch. if (mydebconf_ask (kbd_priority, "console-tools/archs", &arch)) { state = GOBACK; } else { if (arch == NULL || (strlen (arch) == 0)) { di_info ("kbd-chooser: not setting keymap (console-tools/archs not set)"); state = QUIT; break; } di_info ("kbd-chooser: arch %s selected", arch); if ((strcmp (arch, "no-keyboard") == 0) || (strcmp (arch, "skip-config") == 0)) { di_info ("kbd-chooser: not setting keymap"); state = QUIT; break; } state = CHOOSE_KEYMAP; } break; case CHOOSE_KEYMAP: // Then a keymap within that arch. if (keymap_select (arch, keymap) == CMD_GOBACK) { state = CHOOSE_ARCH; } else { di_info ("choose_keymap: keymap = %s", keymap); keymap_set (client, keymap); state = QUIT; } break; case QUIT: break; } } exit (0); }