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