/*
* 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;
}