/* * preloader.c * Installer step that runs after init, but before loader is executed. * The main purpose of preloader is to perform the necessary system * configuration steps in order to execute loader. For example, on s390, * we need to configure the network because the loader is executed as a * login shell over ssh. * * Copyright (C) 2009 Red Hat, Inc. * * 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. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Author(s): David Cantrell */ #include #include #include #include #include #include #include #include #include #include #include "../isys/log.h" #include "preloader.h" #include "loader.h" #include "hardware.h" #include "readvars.h" #include "s390.h" #include "sysfs.h" /* A global hash table that the different preloader steps can make use of. */ static GHashTable *global_conf = NULL; /* * ARCH: all * This function prompts the user for the value to the named key. The * parameters to the function define the text prompt, help text, and * validation regexp that should be used when validating the input. The * value collected is put in the global_conf hash table replacing any * existing value for that key that may have existed. Returns True if * everything worked, False if there were problems. */ static gboolean _ask(gchar *key, gchar *prompt, gchar *help, gchar *dflt, gchar *validation) { gchar *entry = NULL, *value = NULL; GString *tmp_prompt = NULL; if (key == NULL) { return FALSE; } tmp_prompt = g_string_new(NULL); while (TRUE) { /* check for existing value, if it exists and is valid, use it */ value = g_hash_table_lookup(global_conf, key); if (value != NULL && validation != NULL) { if (!g_regex_match_simple(validation, value, 0, 0)) { fprintf(stderr, "*** Invalid entry: %s\n", value); value = NULL; } } /* if there is an existing value, ask user if it should be used */ if (value != NULL) { g_string_printf(tmp_prompt, "Current value: %s\n", value); g_string_append_printf(tmp_prompt, "0) current value, 1) new value"); if (help == NULL) { g_string_append_printf(tmp_prompt, "\n"); } else { g_string_append_printf(tmp_prompt, ", ?) help\n"); } while (TRUE) { entry = readline(tmp_prompt->str); if (!g_strcmp0(entry, "0")) { g_free(entry); g_string_free(tmp_prompt, TRUE); return TRUE; } else if (!g_strcmp0(entry, "1")) { break; } else if (!g_strcmp0(entry, "?") && help != NULL) { printf("%s", help); } else { fprintf(stderr, "*** Invalid entry: %s\n", entry); g_free(entry); } } g_free(entry); printf("\n"); } /* prompt user for new value */ if (prompt == NULL) { g_string_printf(tmp_prompt, "%s:\n", key); entry = readline(tmp_prompt->str); } else { entry = readline(prompt); } /* insert the new value in to the hash table */ if (entry == NULL) { entry = g_strdup(dflt); } g_hash_table_replace(global_conf, key, entry); } return TRUE; } /* * ARCH: s390 * Ask the user what network device to use. Manual configuration causes * this function to return. */ static gboolean _substep_ask_net_device(void) { gboolean have_blacklist = FALSE; gint dev = -1; guint ans, len; gchar *choice = NULL; gchar **nettable = NULL, **line = NULL; GString *prompt = NULL; GError *e = NULL; while (TRUE) { ans = 0; printf("Scanning for available network devices...\n"); nettable = generate_nettable(); len = g_strv_length(nettable); printf("Autodetection found %d devices.\n", len); /* ask user if they want to see detected devices or do manual config */ if (len <= 15) { ans = SHOW_ALL; } else { while (ans < 1) { choice = readline("s) show all, m) manual config: "); if (!g_strcasecmp(choice, "s")) { ans = SHOW_ALL; } else if (!g_strcasecmp(choice, "m")) { ans = MANUAL; } g_free(choice); } } if (ans == MANUAL) { break; } /* display detected devices */ if (len > 0) { line = nettable; printf("\n"); while (*line != NULL) { printf("%s\n", *line); line++; } } /* figure out if we have a device blacklist, alert user */ have_blacklist = is_blacklist_active(); if (have_blacklist) { printf("Note: There is a device blacklist active! (Clearing might take long)\n"); } else if (!have_blacklist && len == 0) { printf("Entering manual configuration mode.\n"); break; } /* prompt user for network device */ prompt = g_string_new("m) manual config, r) rescan, s) shell: "); if (len > 0) { g_string_prepend(prompt, ") use config, "); } if (have_blacklist) { g_string_prepend(prompt, "c) clear blacklist, "); } while (TRUE) { choice = readline(prompt->str); if (choice == NULL || !g_strcasecmp(choice, "m")) { ans = MANUAL; break; } else if (!g_strcasecmp(choice, "s")) { printf("Enter 'exit' at the shell prompt to get back to the installation dialog.\n"); if (!g_spawn_command_line_sync("/bin/bash", NULL, NULL, NULL, &e)) { fprintf(stderr, "*** %s\n", e->message); } break; } else if (!g_strcasecmp(choice, "r")) { break; } else if (have_blacklist && !g_strcasecmp(choice, "c")) { printf("Clearing device blacklist..."); free_from_blacklist("all"); break; } else { errno = 0; dev = strtol(choice, NULL, 10); if ((errno == ERANGE && (dev == LONG_MIN || dev == LONG_MAX)) || (errno != 0 && dev == 0)) { fprintf(stderr, "%s: %d: %m", __func__, __LINE__); } if (dev >= 1 && dev <= len) { break; } else { fprintf(stderr, "*** invalid device selected: %d\n", dev); } } g_free(choice); } if (ans == MANUAL) { break; } /* select the network device chosen by the user */ if (dev >= 1 && dev <= len) { gchar **fields = g_strsplit(nettable[dev - 1], " ", -1); if (g_strv_length(fields) < 8) { fprintf(stderr, "*** invalid network device line\n"); g_strfreev(fields); } else { /* field 4 is NETTYPE */ if (!g_strcmp0(fields[4], "ctcm")) { g_hash_table_replace(global_conf, "NETTYPE", "ctc"); } else { g_hash_table_replace(global_conf, "NETTYPE", fields[4]); } /* field 6 is SUBCHANNELS */ g_hash_table_replace(global_conf, "SUBCHANNELS", fields[6]); g_strfreev(fields); break; } } } printf("\n"); g_strfreev(nettable); return TRUE; } /* * ARCH: s390 * If CMSDASD and CMSCONFFILE are on the kernel boot command line, read in * the named configuration file from the specified DASD and store the * contents in global_conf. */ gboolean step_read_cms_file(gpointer user_data) { gint i = 0; gchar *cmsdasd = NULL, *cmsconffile = NULL; GError *e = NULL; GHashTable *cmdline = readvars_parse_file(PROC_CMDLINE); gpointer value = NULL; if (g_hash_table_lookup_extended(cmdline, "CMSDASD", NULL, value)) { cmsdasd = g_strdup((gchar *) value); } if (g_hash_table_lookup_extended(cmdline, "CMSCONFFILE", NULL, value)) { cmsconffile = g_strdup((gchar *) value); } g_hash_table_destroy(cmdline); if (cmsdasd != NULL && cmsconffile != NULL) { gchar *so = NULL, *se = NULL; gchar *dasd = normalize_ccw_devno(cmsdasd); GString *tmp = g_string_new(NULL); g_free(cmsdasd); if (!dasd_cio_free(dasd)) { logMessage(ERROR, "DASD %s could not be cleared from device blacklist: %s", dasd, e->message); g_free(cmsconffile); g_free(dasd); return FALSE; } /* XXX: dasd block until it's up, used to be * 'udevadm settle && sleep 1' */ if (!write_sysfs_property("1", SYSFS_CCW_DEVICES, dasd, "online")) { g_free(cmsconffile); g_free(dasd); return FALSE; } /* XXX: udevadm settle */ if (dasd_settle(dasd) == FALSE) { logMessage(ERROR, "Could not access DASD %s in time", dasd); g_free(cmsconffile); g_free(dasd); return FALSE; } /* XXX: udevadm settle */ g_string_append_printf(tmp, "%s -d /dev/dasda -a %s", CMD_CMSFSCAT, cmsconffile); if (g_spawn_command_line_sync(tmp->str, &so, &se, &i, &e) == TRUE) { if (WIFEXITED(i) && WEXITSTATUS(i)) { logMessage(ERROR, "Could not read conf file %s on CMS DASD %s: %s", cmsconffile, dasd, se); } else { global_conf = readvars_parse_string(so); } g_free(so); g_free(se); } else { logMessage(ERROR, "Could not read conf file %s on CMS DASD %s: %s", cmsconffile, dasd, e->message); } if (!write_sysfs_property("0", SYSFS_CCW_DEVICES, dasd, "online")) { g_free(cmsconffile); g_free(dasd); g_string_free(tmp, TRUE); return FALSE; } /* XXX: udevadm settle */ g_string_free(tmp, TRUE); } g_free(cmsdasd); g_free(cmsconffile); return TRUE; } /* * ARCH: s390 * Check IPL method. If we see FCP, ask the user if they want to install * from CD/DVD media. */ gboolean step_check_ipl_type(gpointer user_data) { gchar *ipl_type = read_sysfs_property(SYSFS_FIRMWARE_IPL, "ipl_type"); if (ipl_type == NULL) { return FALSE; } if (!g_strcmp0(ipl_type, "fcp")) { gboolean ask = TRUE; gchar *answer = NULL; gchar *cd_device = NULL, *wwpn = NULL, *lun = NULL; while (ask) { printf("Your IPL device is set to FCP.\n"); answer = readline("Would you like to perform a CD-ROM or " "DVD-ROM installation? (y/n) "); if (!strcasecmp(answer, "y") || !strcasecmp(answer, "yes")) { cd_device = read_sysfs_property(SYSFS_FIRMWARE_IPL, "device"); wwpn = read_sysfs_property(SYSFS_FIRMWARE_IPL, "wwpn"); lun = read_sysfs_property(SYSFS_FIRMWARE_IPL, "lun"); if (!zfcp_cio_free(cd_device, wwpn, lun)) { logMessage(ERROR, "device %s could not be cleared from " "the device blacklist", cd_device); ask = FALSE; continue; } else { /* XXX: udevadm settle */ /* XXX: sleep 1 */ } if (!write_sysfs_property("1", SYSFS_ZFCP_DRIVER, cd_device, "online")) { logMessage(ERROR, "could not set FCP device %s online", cd_device); ask = FALSE; continue; } /* XXX: udevadm settle */ if (!write_sysfs_property(lun, SYSFS_ZFCP_DRIVER, cd_device, wwpn, "unit_add")) { logMessage(ERROR, "could not add LUN %s at WWPN %s on " "FCP device %s", lun, wwpn, cd_device); ask = FALSE; continue; } /* XXX: udevadm settle */ ask = FALSE; } else if (!strcasecmp(answer, "n") || !strcasecmp(answer, "no")) { ask = FALSE; } else { printf("\n** INVALID ANSWER: %s\n\n", answer); g_free(answer); } } g_free(answer); g_free(cd_device); g_free(wwpn); g_free(lun); } g_free(ipl_type); return TRUE; } /* * ARCH: s390 * Tell user the CHANDEV variable is deprecated. */ gboolean step_chandev_param(gpointer user_data) { printf("The CHANDEV variable is not used anymore, please update your\n"); printf(".parm or .conf file to use NETTYPE, SUBCHANNELS, etc. instead\n"); return TRUE; } /* * ARCH: s390 * Tell user the NETWORK variable is deprecated. */ gboolean step_network_param(gpointer user_data) { printf("The NETWORK variable is not used anymore and will be ignored.\n"); printf("It is sufficient to only specify IPADDR and NETMASK.\n"); return TRUE; } /* * ARCH: s390 * Tell user the BROADCAST variable is deprecated. */ gboolean step_broadcast_param(gpointer user_data) { printf("The BROADCAST variable is not used anymore and will be ignored.\n"); printf("It is sufficient to only specify IPADDR and NETMASK.\n"); return TRUE; } /* * ARCH: s390 * System configuration loop. Prompt the user for different configuration * parameters and only continue once the user is satisfied. */ gboolean step_configuration_loop(gpointer user_data) { gboolean ask = TRUE, result = FALSE; gchar *value = NULL; while (ask) { /* Only prompt for network device under certain conditions */ if (g_hash_table_lookup(global_conf, "RUNKS") == NULL && (g_hash_table_lookup(global_conf, "NETTYPE") == NULL || g_hash_table_lookup(global_conf, "SUBCHANNELS") == NULL)) { _substep_ask_net_device(); } /* If installation is running on a z/VM guest, give more help */ if (is_zvm()) { printf("* NOTE: To enter default or empty values, press enter twice. n"); } /* Prompt for NETTYPE */ if (!_ask("NETTYPE", NETTYPE_PROMPT, NETTYPE_HELP, "qeth", NETTYPE_VALIDATION)) { fprintf(stderr, "*** error prompting for NETTYPE\n\n"); continue; } /* Prompt for SUBCHANNELS */ if ((value = g_hash_table_lookup(global_conf, "NETTYPE")) == NULL) { value = "qeth"; } if (!g_strcmp0(value, "qeth")) { result = _ask("SUBCHANNELS", SUBCHANNELS_PROMPT_QETH, SUBCHANNELS_HELP_QETH, "qeth", SUBCHANNELS_VALIDATION_QETH); } else { result = _ask("SUBCHANNELS", SUBCHANNELS_PROMPT_LCS_CTC, SUBCHANNELS_HELP_LCS_CTC, "lcs", SUBCHANNELS_VALIDATION_LCS_CTC); } if (!result) { fprintf(stderr, "*** error prompting for SUBCHANNELS\n\n"); continue; } if (semantic_check_subchannels(global_conf)) { } else { fprintf(stderr, "*** unable to use SUBCHANNELS specified\n\n"); continue; } /* XXX: semantic_check_subchannels, handle_subchannels, finish_subchannels */ /* XXX: here's where things lie */ } return TRUE; } /* MAIN */ int main(int argc, char **argv) { gint i = 0; size_t archlen = 0; struct utsname ubuf; /* get hardware detection backends running early */ if (busProbe(0) != LOADER_OK) { fprintf(stderr, "failed probing for hardware, exiting\n"); return EXIT_FAILURE; } /* figure out what architecture we are on */ if (uname(&ubuf) == -1) { fprintf(stderr, "%s line %d: %m\n", __func__, __LINE__); return EXIT_FAILURE; } else { archlen = strlen(ubuf.machine); } /* run all preloader steps for this architecture */ while (steps[i].function != NULL) { if (steps[i].arch == NULL || !strncmp(ubuf.machine, steps[i].arch, archlen)) { steps[i].function(NULL); } i++; } return EXIT_SUCCESS; }