/*
* s390.c
* Utility functions specific to the s390 platform.
*
* 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 "s390.h"
#include "sysfs.h"
/*
* Verify the specified cutype is correct for our NETTYPE.
* XXX: this is from /lib/s390-utils/znetcontrolunits, which is shell and
* unusable here. the s390-tools should be providing something for
* this that we can use in C.
*/
static gboolean _validate_cutype(gchar *nettype, gchar *cutype) {
if (nettype == NULL || cutype == NULL) {
return FALSE;
}
if (!g_strcmp0(nettype, "ctcm") || !g_strcmp0(nettype, "ctc")) {
if (!g_strcmp0(cutype, "3088/08") ||
!g_strcmp0(cutype, "3088/1f") ||
!g_strcmp0(cutype, "3088/1e")) {
return TRUE;
} else {
return FALSE;
}
} else if (!g_strcmp0(nettype, "lcs")) {
if (!g_strcmp0(cutype, "3088/01") ||
!g_strcmp0(cutype, "3088/60") ||
!g_strcmp0(cutype, "3088/61")) {
return TRUE;
} else {
return FALSE;
}
} else if (!g_strcmp0(nettype, "qeth")) {
if (!g_strcmp0(cutype, "1731/01") ||
!g_strcmp0(cutype, "1731/05") ||
!g_strcmp0(cutype, "1731/06")) {
return TRUE;
} else {
return FALSE;
}
} else {
return FALSE;
}
}
/*
* Compose a string of CHPIDs configured to work with the pim value from
* the given pimpampom value string. Caller is responsible for freeing
* value returned.
*/
static gchar *_get_pimchpid(gchar *pimpampom, gchar **chpids) {
glong chp = 0, pim = 0;
gchar *chpptr = NULL;
gchar **fields = NULL;
gchar *ret = NULL;
if (pimpampom == NULL || chpids == NULL) {
return FALSE;
}
fields = g_strsplit(pimpampom, " ", -1);
if (fields == NULL || g_strv_length(fields) < 3) {
g_strfreev(fields);
return FALSE;
} else {
errno = 0;
pim = strtol(fields[0], NULL, 16);
g_strfreev(fields);
if ((errno == ERANGE &&
(errno == LONG_MIN || errno == LONG_MAX)) ||
(errno != 0 && pim == 0)) {
fprintf(stderr, "*** %s: %d: %m\n", __func__, __LINE__);
return FALSE;
}
}
while (((chpptr = *chpids) != NULL)) {
errno = 0;
chp = strtol(chpptr, NULL, 16);
if ((errno == ERANGE &&
(errno == LONG_MIN || errno == LONG_MAX)) ||
(errno != 0 && chp == 0)) {
fprintf(stderr, "*** %s: %d: %m\n", __func__, __LINE__);
return FALSE;
}
if (pim & (0x80 >> chp)) {
if (ret == NULL) {
ret = g_strdup(chpptr);
} else {
ret = g_strconcat(ret, chpptr, NULL);
}
}
chpids++;
}
return ret;
}
/*
* Free memory associated with a subchannel_t structure.
*/
static void _free_subchannel_t(subchannel_t *sc) {
if (sc == NULL) {
return;
}
g_free(sc->dev_p);
g_free(sc->subch_p);
g_free(sc->subch);
g_free(sc->nettype);
g_gree(sc->prefix);
g_free(sc->ssid);
g_free(sc->devno);
g_free(sc->pimchipids);
g_free(sc->cutype);
g_strfreev(sc->chpids);
sc = NULL;
return
}
/*
* Wrapper for dasd_cio_free() and zfcp_cio_free()
*/
static gboolean _device_cio_free(gchar *cmd, gchar *conf, gchar *contents) {
gint status = 0;
gchar *so = NULL, *se = NULL;
GError *e = NULL;
gboolean ret = TRUE;
if (cmd == NULL || conf == NULL || contents == NULL) {
return FALSE;
}
if (!g_file_set_contents(conf, contents, -1, &e)) {
return FALSE;
}
if (g_spawn_command_line_sync(cmd, &so, &se, &status, &e)) {
if (WIFEXITED(status) && WEXITSTATUS(status)) {
fprintf(stderr, "%s\n", se);
ret = FALSE;
}
g_free(so);
g_free(se);
} else {
fprintf(stderr, "%s\n", e->message);
ret = FALSE;
}
return ret;
}
/*
* Free a DASD from the cio_ignore blacklist.
*/
gboolean dasd_cio_free(gchar *dasd) {
gboolean ret = FALSE;
GString *tmp = g_string_new(NULL);
if (dasd == NULL) {
return FALSE;
} else {
g_string_append_printf(tmp, "%s\n", dasd);
}
/* XXX: current s390utils release requires /etc/dasd.conf, eventually
* the command should be able to take a device list on the command line
*/
ret = _device_cio_free(DASD_CIO_FREE, DASD_CONF, tmp->str);
g_string_free(tmp, TRUE);
return ret;
}
/*
* Free a zFCP device from the cio_ignore blacklist.
*/
gboolean zfcp_cio_free(gchar *devno, gchar *wwpn, gchar *lun) {
gboolean ret = FALSE;
GString *tmp = g_string_new(NULL);
if (devno == NULL || wwpn == NULL || lun == NULL) {
return FALSE;
} else {
g_string_append_printf(tmp, "%s %s %s\n", devno, wwpn, lun);
}
/* XXX: current s390utils release requires /etc/zfcp.conf, eventually
* the command should be able to take a device list on the command line
*/
ret = _device_cio_free(ZFCP_CIO_FREE, ZFCP_CONF, tmp->str);
g_string_free(tmp, TRUE);
return ret;
}
/*
* Given a CCW device number, normalize it in to the proper x.y.z format.
* Caller is responsible for freeing the returned string.
*
* Valid input: 201, 3.A45, 0.1.1234, a.1.b3c5
* Invalid input: .21, ., .., .0.
*/
gchar *normalize_ccw_devno(gchar *ccw) {
gchar *ret = NULL;
gchar *regex = "^[:xdigit:]\\.[:xdigit:]\\.[:xdigit:]{4}$";
guint len = 0;
gchar **parts = NULL;
GString *tmp = NULL;
if (ccw == NULL) {
return NULL;
} else {
ccw = g_ascii_strup(ccw, -1);
}
if (g_regex_match_simple(regex, ccw, G_REGEX_CASELESS, 0)) {
ret = g_strdup(ccw);
return ret;
}
parts = g_strsplit(ccw, ".", 0);
len = g_strv_length(parts);
tmp = g_string_new(NULL);
if (len == 1 && !g_strcmp0(ccw, parts[0]) && strlen(ccw) <= 4) {
g_string_printf(tmp, "0.0.%4s", ccw);
ret = tmp->str;
} else if (len == 2 && strlen(parts[0]) == 1 && strlen(parts[1]) <= 4) {
g_string_printf(tmp, "0.%s.%4s", parts[0], parts[1]);
ret = tmp->str;
} else if (len == 3 && strlen(parts[0]) == 1 &&
strlen(parts[1]) == 1 && strlen(parts[2]) <= 4) {
g_string_printf(tmp, "%s.%s.%4s", parts[0], parts[1], parts[2]);
ret = tmp->str;
} else {
g_free(ret);
ret = NULL;
}
if (ret) {
ret = g_strcanon(ret, "ABCDEF0123456789.", '0');
}
g_string_free(tmp, FALSE);
g_strfreev(parts);
return ret;
}
/*
* Wait for the specified DASD device to complete initialization. Eventually
* time out if the device never comes online.
*/
gboolean dasd_settle(gchar *dasd) {
gint i = 0;
GString *tmp = NULL;
if (dasd == NULL) {
return FALSE;
}
tmp = g_string_new(SYSFS_CCW_DEVICES);
g_string_append_printf(tmp, "/%s/status", dasd);
if (access(tmp->str, R_OK)) {
g_string_free(tmp, TRUE);
return FALSE;
}
while (i < 30) {
gchar *input = NULL;
gsize len = 0;
GError *e = NULL;
if (g_file_get_contents(tmp->str, &input, &len, &e) == FALSE) {
fprintf(stderr, "%s line %d: %s\n", __func__, __LINE__, e->message);
} else if (!g_strcmp0(input, "online") ||
!g_strcmp0(input, "unformatted")) {
g_free(input);
g_string_free(tmp, TRUE);
return TRUE;
}
g_free(input);
usleep(100000);
i++;
}
g_free(tmp);
g_string_free(tmp, TRUE);
return FALSE;
}
/*
* Returns true if we are running under z/VM.
*/
gboolean is_zvm(void) {
gsize len = 0;
gchar *contents = NULL;
gchar **lines = NULL, **line = NULL;
GError *e = NULL;
gboolean ret = FALSE;
if (!g_file_get_contents(PROC_CPUINFO, &contents, &len, &e)) {
fprintf(stderr, "*** error reading %s: %s\n", PROC_CPUINFO, e->message);
}
line = lines = g_strsplit(contents, "\n", -1);
while (*line != NULL) {
if (g_str_has_prefix(*line, "processor ") &&
g_strstr_len(*line, -1, "version = FF") != NULL) {
ret = TRUE;
break;
}
line++;
}
g_free(contents);
g_strfreev(lines);
return ret;
}
/*
* Read the output of lsznet.raw from s390-utils and build a table of
* network devices. Caller is responsible for freeing the string array
* returned.
*/
gchar **generate_nettable(void) {
gint status = 0;
gchar *so = NULL, *se = NULL;
GError *e = NULL;
GString *fmt = NULL;
gchar *fmtstr = "%3s %-14s %-7s %-5s %-4s %-6s %-7s %s";
gchar **table = NULL, **line = NULL;
if (!g_spawn_command_line_sync(LSZNET_RAW, &so, &se, &status, &e)) {
g_free(so);
g_free(se);
return NULL;
}
fmt = g_string_new(NULL);
g_string_printf(fmt, fmtstr, "NUM", "CARD", "CU", "CHPID", "TYPE",
"DRIVER", "IF", "DEVICES");
so = g_strstrip(g_strconcat(fmt->str, "\n", so, NULL));
g_string_erase(fmt, 0, -1);
table = line = g_strsplit(so, "\n", -1);
while (*line != NULL) {
gint i = 0;
gchar **fields = NULL, **field = NULL;
gchar *cols[8];
if (g_str_has_prefix(*line, "NUM")) {
line++;
continue;
}
field = fields = g_strsplit(*line, " ", -1);
if (g_strv_length(fields) < 8) {
g_strfreev(fields);
continue;
}
while (*field != NULL) {
if (i == 0) {
cols[i] = *field;
} else if (i >= 1 && i <= 6) {
cols[i+1] = *field;
} else if (i == 7) {
cols[1] = g_strstrip(g_strjoinv(" ", field));
}
field++;
i++;
}
g_free(*line);
g_string_printf(fmt, fmtstr, cols[0], cols[1], cols[2], cols[3],
cols[4], cols[5], cols[6], cols[7]);
*line = g_strdup(fmt->str);
g_free(cols[1]);
g_strfreev(fields);
line++;
}
g_string_free(fmt, TRUE);
g_free(so);
g_free(se);
return table;
}
/*
* Tells us if there is a device blacklist active or not.
* XXX: this should use /sbin/cio_ignore
*/
gboolean is_blacklist_active(void) {
gsize len = 0;
gchar *contents = NULL;
gboolean ret = FALSE;
GError *e = NULL;
if (!g_file_get_contents(PROC_CIO_IGNORE, &contents, &len, &e)) {
fprintf(stderr, "*** %s: %s\n", PROC_CIO_IGNORE, e->message);
} else if (len > 0) {
ret = TRUE;
}
g_free(contents);
return ret;
}
/*
* Removes specified device from the blacklist. "all" is a valid device
* if you want to remove all currently blacklisted devices.
*/
void free_from_blacklist(gchar *device) {
GString *tmp = NULL;
GError *e = NULL;
if (device == NULL) {
return;
} else {
tmp = g_string_new("free ");
g_string_append_printf(tmp, "%s", device);
}
if (g_file_set_contents(PROC_CIO_IGNORE, tmp->str, -1, &e)) {
/* XXX: udevadm settle */
/* XXX: sleep 3 */
} else {
if (!g_strcmp0(device, "all")) {
fprintf(stderr, "Device blacklist could not be cleared:");
fprintf(stderr, " %s\n", e->message);
} else {
fprintf(stderr, "Device %s could not be cleared from ", device);
fprintf(stderr, "device blacklist\n");
}
}
g_string_free(tmp, TRUE);
return;
}
/*
* Verify that the SUBCHANNELS we have are valid and usable for the
* NETTYPE currently set.
*/
gboolean semantic_check_subchannels(GHashTable *global_conf) {
gboolean ret = TRUE;
gint numfields = 0, i = 0;
gchar *nettype = g_hash_table_lookup(global_conf, "NETTYPE");
gchar *subchannels = g_hash_table_lookup(global_conf, "SUBCHANNELS");
gchar **subchannel = NULL;
subchannel_t first_sc, any_sc;
subchannel_t *sc = NULL;
if (nettype == NULL || subchannels == NULL) {
return FALSE;
}
if (!g_strcmp0(nettype, "qeth")) {
numfields = 3;
} else {
numfields = 2;
}
subchannel = g_strsplit(subchannels, ",", -1);
if (subchannel == NULL || g_strv_length(subchannel) != numfields) {
return FALSE;
}
for (i = 0; i < numfields; i++) {
gchar *value = NULL;
gchar **ccwfields = g_strsplit(subchannel[i], ".", -1);
GString *tmp = g_string_new(NULL);
if (i == 0) {
sc = &first_sc;
g_hash_table_replace(global_conf, "SCH_R_DEVBUSID",
g_strdup(subchannel[i]));
} else {
sc = &any_sc;
}
/* break x.y.z subchannel in to components */
if (ccwfields == NULL || g_strv_length(ccwfields) != 3) {
g_strfreev(subchannel);
g_string_free(tmp, TRUE);
return FALSE;
} else {
sc->prefix = g_strdup(ccwfields[0]);
sc->ssid = g_strdup(ccwfields[1]);
sc->devno = g_strdup(ccwfields[2]);
g_strfreev(ccwfields);
}
/* check for existence of devno in sysfs */
g_string_append_printf(tmp, "/sys/devices/css%s", sc->prefix);
sc->dev_p = sysfs_find_bus_by_component(tmp->str, subchannel[i]);
if (sc->dev_p == NULL && is_blacklist_active()) {
printf("Device %s not present, trying to clear ", subchannel[i]);
printf("from blacklist and resense...\n");
free_from_blacklist(subchannel[i]);
}
sc->dev_p = sysfs_find_bus_by_component(tmp->str, subchannel[i]);
if (sc->dev_p == NULL) {
printf("Device %s does not exist\n", subchannel[i]);
goto invalid_subchannel;
}
/* devno does exist now */
/* filter unusable subchannels */
sc->subch_p = dirname(g_strdup(sc->dev_p));
sc->subch = basename(g_strdup(sc->subch_p));
/* - check for subchannel I/O */
value = read_sysfs_property(sc->subch_p, "type");
if (value == NULL || g_strcmp0(value, SUBCHANNEL_TYPE_IO)) {
printf("Channel %s (device %s) is not of type I/O\n",
sc->subch, subchannel[i]);
goto invalid_subchannel;
} else {
g_free(value);
}
/* - check for correct CU type/model, depending on qeth/lcs/ctc */
sc->cutype = read_sysfs_property(sc->dev_p, "cutype");
if (sc->cutype == NULL) {
printf("Device %s does not have required sysfs ", subchannel[i]);
printf("attribute 'cutype'\n");
goto invalid_subchannel;
}
sc->nettype = g_hash_table_lookup(global_conf, "NETTYPE");
if (!_validate_cutype(sc->nettype, value)) {
printf("Device %s has control unit type %s,\n",
subchannel[i], value);
printf(" which does not match your selected network type %s\n",
sc->nettype);
goto invalid_subchannel;
}
/* - read CHPIDs information about subchannels */
value = read_sysfs_property(sc->subch_p, "chpids");
if (value == NULL) {
printf("Channel %s (device %s) does not ",
sc->subch, subchannel[i]);
printf("have required sysfs attribute 'chpids'\n");
goto invalid_subchannel;
} else {
sc->chpids = g_strsplit(value, " ", -1);
gint len = g_strv_length(sc->chpids);
if (len != 8) {
printf("sysfs reported %d CHPIDs instead of expected ", len);
printf("8, code needs fix\n");
}
g_free(value);
}
/* - validate pimpampom */
value = read_sysfs_property(sc->subch_p, "pimpampom");
if (value == NULL) {
printf("Channel %s (device %s) does ", sc->subch, subchannel[i]);
printf("not have required sysfs attribute 'pimpampom'\n");
goto invalid_subchannel;
} else {
sc->pimchpid = get_pimchpid(value, sc->chpids);
if (sc->pimchpid == NULL) {
printf("Channel %s (device %s) ", sc->subch, subchannel[i]);
printf("does not have any installed channel path\n");
goto invalid_subchannel;
}
g_free(value);
}
/* compare first subchannel to other subchannels */
if (i > 0) {
if (g_strcmp0(first_sc.cutype, sc->cutype)) {
printf("Device %s does not have the same ", subchannel[i]);
printf("control unit type as device %s\n", first_sc.devbusid);
}
if (g_strcmp0(first_sc.pimchpids, sc->pimchpids)) {
printf("Device %s does not have the same ", subchannel[i]);
printf("CHPIDs as device %s\n", first_sc.devbusid);
}
if (g_strcmp0(first_sc.prefix, sc->prefix) ||
g_strcmp0(first_sc.ssid, sc->ssid)) {
printf("Device %s does not have the same ", subchannel[i]);
printf("prefix and subchannel set ID as device ");
printf("%s\n", first_sc.devbusid);
}
good = FALSE;
}
goto next_subchannel;
invalid_subchannel:
ret = FALSE;
next_subchannel:
if (i > 0) {
_free_subchannel_t(sc);
}
g_free(value);
g_string_free(tmp, TRUE);
}
return ret;
}