/** * lsqeth - command of s390-tools * * List qeth-based network devices with their attributes * * Copyright IBM Corp. 2004, 2017 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "lib/util_base.h" #include "lib/util_file.h" #include "lib/util_opt.h" #include "lib/util_libc.h" #include "lib/util_panic.h" #include "lib/util_path.h" #include "lib/util_prg.h" #include "lib/util_rec.h" #include "lib/util_scandir.h" #include "lib/zt_common.h" #include "lib/vmcp.h" #include "misc.h" #define ID_FORMAT "^[[:xdigit:]]{1,2}[.][[:xdigit:]][.][[:xdigit:]]{4}$" #define MAX_ID_LENGTH 10 #define PAGE_SIZE 4096 /* * Private data */ static struct lsqeth_cmd_flags { bool proc_format; } cmd; /* * Program configuration */ const struct util_prg prg = { .desc = "List all qeth-based network devices with their corresponding settings.\n" "\nINTERFACE" "\n List only attributes of specified interface", .args = "[INTERFACE]", .copyright_vec = { { .owner = "IBM Corp.", .pub_first = 2017, .pub_last = 2017, }, UTIL_PRG_COPYRIGHT_END } }; /* * Command line options */ static struct util_opt opt_vec[] = { UTIL_OPT_SECTION("OPTIONS"), { .option = { "proc", no_argument, NULL, 'p'}, .desc = "List all devices in the former /proc/qeth format" }, UTIL_OPT_HELP, UTIL_OPT_VERSION, UTIL_OPT_END }; /* * Exchange content of field value via simple translation map * * If the attribute is required for translation (attibute name is present in * tr_attr_list[]) then the value of the attribute (if matches the dictionary * translation key) is exchanged by the target value of the translation * dictionary (sys_to_proc_dict[][2]). * */ static void tr_to_proc_format(char *val, const char *attr_name) { static const char *tr_attr_list[] = { "checksumming", "priority_queueing", "route4", "route6" }; static const char *sys_to_proc_dict[][2] = { {"sw checksumming", "sw"}, {"hw checksumming", "hw"}, {"no checksumming", "no"}, {"always queue 0", "always_q_0"}, {"always queue 1", "always_q_1"}, {"always queue 2", "always_q_2"}, {"always queue 3", "always_q_3"}, {"by precedence", "by_prec."}, {"by type of service", "by_ToS"}, {"by skb-priority", "by_skb"}, {"by VLAN headers", "by_vlan"}, {"primary router", "pri"}, {"secondary router", "sec"}, {"primary connector+", "p+c"}, {"primary connector", "p.c"}, {"secondary connector+", "s+c"}, {"secondary connector", "s.c"}, {"multicast router+", "mc+"}, {"multicast router", "mc"}, {NULL, NULL} }; unsigned int i = 0; if (misc_str_in_list(attr_name, tr_attr_list, ARRAY_SIZE(tr_attr_list))) { while (sys_to_proc_dict[i][0]) { if (strcmp(val, sys_to_proc_dict[i][0]) == 0) { strcpy(val, sys_to_proc_dict[i][1]); return; } i++; } } } /* * Read IPv4 and IPv6 addresses from related sysfs entries for ipa/parp/vipa and * store each value at the end of *argz array. Return the number of entries stored. */ static int get_qethconf(const char *name, const char *if_name, char **argz, size_t *argz_len) { char *path; int count; path = util_path_sysfs("class/net/%s/device/%s/add4", if_name, name); count = misc_argz_add_from_file(argz, argz_len, path); free(path); path = util_path_sysfs("class/net/%s/device/%s/add6", if_name, name); count += misc_argz_add_from_file(argz, argz_len, path); free(path); return count; } /* * Get checksumming information for specified interface */ static void ethtool_checksumming(char *buf, const char *if_name) { struct ethtool_value val; struct ifreq ifr; int fd, rc; fd = socket(AF_INET, SOCK_DGRAM, 0); if (fd < 0) errx(EXIT_FAILURE, "Internal error: cannot get SOCK_DGRAM socket"); util_strlcpy(ifr.ifr_name, if_name, IFNAMSIZ); val.cmd = ETHTOOL_GRXCSUM; ifr.ifr_data = (void *)&val; rc = ioctl(fd, SIOCETHTOOL, &ifr); close(fd); if (!rc && val.data) strcpy(buf, "hw"); else strcpy(buf, "sw"); } /* * Call cp command and return the resulting lines as argz vector * * @param[in,out] argz argz vector allocated by argz_create * so the caller should free the allocated memory * @param[in,out] argz_len argz length * @param[in] fmt Format string specifying the cp command */ static void exec_cp(char **argz, size_t *argz_len, const char *fmt, ...) { struct vmcp_parm cp; char *command; va_list ap; int rc; UTIL_VASPRINTF(&command, fmt, ap); cp.cpcmd = command; cp.do_upper = false; cp.buffer_size = VMCP_DEFAULT_BUFSZ; rc = vmcp(&cp); free(command); switch (rc) { case VMCP_ERR_OPEN: errx(EXIT_FAILURE, "Could not open device %s", VMCP_DEVICE_NODE); case VMCP_ERR_SETBUF: errx(EXIT_FAILURE, "Could not set buffer size"); case VMCP_ERR_GETCODE: errx(EXIT_FAILURE, "Could not query return code"); case VMCP_ERR_GETSIZE: errx(EXIT_FAILURE, "Could not query response size"); case VMCP_ERR_WRITE: errx(EXIT_FAILURE, "Could not issue CP command"); case VMCP_ERR_READ: errx(EXIT_FAILURE, "Could not read CP response"); case VMCP_ERR_TOOSMALL: break; /* Truncated CP response treated as good */ } rc = argz_create_sep(cp.response, '\n', argz, argz_len); if (rc) errx(EXIT_FAILURE, "Memory allocation error for CP response processing"); free(cp.response); } /* * Return card_type attribute value based on 'vmcp q v nic' output */ static char *print_LAN(const char *cdev0) { char *tok_vec[5], *tk, *result, *retstr, *devno, *entry; char *tail = "not coupled"; size_t argz_len = 0; char *argz = NULL; unsigned int i; devno = strrchr(cdev0, '.') + 1; exec_cp(&argz, &argz_len, "QUERY VIRTUAL NIC %s", devno); /* Process CP command output in argz format */ if (argz_count(argz, argz_len) < 2) errx(EXIT_FAILURE, "Internal error: Unexpected result of 'vmcp q v nic' command"); /* Tokenize second line */ entry = argz_next(argz, argz_len, argz); memset(tok_vec, 0, sizeof(tok_vec)); tk = strtok(entry, " \t"); for (i = 0; i < ARRAY_SIZE(tok_vec) && tk; i++) { tok_vec[i] = tk; tk = strtok(NULL, " \t"); } util_asprintf(&result, "%s %s %s", tok_vec[2], tok_vec[3], tok_vec[4]); /* Tokenize first line */ entry = argz; memset(tok_vec, 0, sizeof(tok_vec)); tk = strtok(entry, " \t"); for (i = 0; i < ARRAY_SIZE(tok_vec) && tk; i++) { tok_vec[i] = tk; tk = strtok(NULL, " \t"); } if (strncmp(result, "LAN", 3) == 0) { if (strncmp(result + 5, "* Internal", 11) != 0) tail = result + 5; util_asprintf(&retstr, "GuestLAN: %s (%s %s)", tail, tok_vec[2], tok_vec[3]); } else { util_asprintf(&retstr, "%s (%s %s)", result, tok_vec[2], tok_vec[3]); } free(result); free(argz); return retstr; } /* * Update card_type field for Virtual NIC devices */ static void update_card_type(struct util_rec *rec) { const char *card_type, *cdev0; char *upd_card_type; if (!util_path_is_readable("/dev/vmcp")) return; card_type = util_rec_get(rec, "card_type"); if (!card_type) return; if (strncmp(card_type, "GuestLAN", 8) == 0 || strncmp(card_type, "Virt.NIC", 8) == 0) { cdev0 = util_rec_get(rec, "cdev0"); if (!cdev0) return; upd_card_type = print_LAN(cdev0); util_rec_set(rec, "card_type", upd_card_type); free(upd_card_type); } } /* * Process regular sysfs attribute and save it to the record. */ static void process_sysfs_attribute(struct util_rec *rec, const char *path, const char *attr_name, const char *if_name) { char buf[PAGE_SIZE]; char *link = NULL; /* Process cdev attributes (for normal output format only) */ if (strncmp(attr_name, "cdev", 4) == 0 && !cmd.proc_format) { link = misc_link_target("%s/%s", path, attr_name); /* If no simlink found, cdev* record field will not be set */ if (link) { util_rec_set(rec, attr_name, basename(link)); free(link); } } /* Process other sysfs attributes */ if (util_file_read_line(buf, sizeof(buf), "%s/%s", path, attr_name) == 0 && strlen(buf) != 0 && strcmp(buf, "n/a") != 0) { /* Translate attribute values to proc-format if required */ if (cmd.proc_format) tr_to_proc_format(buf, attr_name); /* Hex notation for 'chpid' attribute in proc format */ if (strcmp(attr_name, "chpid") == 0 && cmd.proc_format) util_rec_set(rec, attr_name, "x%s", buf); else util_rec_set(rec, attr_name, buf); } else { /* Special case for 'checksumming' and 'route6' */ if (strcmp(attr_name, "checksumming") == 0) { if (cmd.proc_format) { ethtool_checksumming(buf, if_name); util_rec_set(rec, attr_name, buf); } } else if (strcmp(attr_name, "route6") == 0 && strcmp(buf, "n/a") == 0) { util_rec_set(rec, attr_name, "no"); } else { /* Set 'n/a' default value for proc format*/ if (cmd.proc_format) util_rec_set(rec, attr_name, "n/a"); } } } /* * Set ipa/parp/vipa attributes for normal format output. */ static void set_ipa_vipa_parp(struct util_rec *rec, const char *attr_name, const char *path, const char *if_name) { size_t argz_len = 0; char *argz = NULL; if (strcmp(attr_name, "ipa") == 0) { if (!util_path_is_dir("%s/ipa_takeover", path)) return; if (get_qethconf("ipa_takeover", if_name, &argz, &argz_len)) util_rec_set_argz(rec, attr_name, argz, argz_len); } else if (strcmp(attr_name, "parp") == 0) { if (!util_path_is_dir("%s/rxip", path)) return; if (get_qethconf("rxip", if_name, &argz, &argz_len)) util_rec_set_argz(rec, attr_name, argz, argz_len); } else { if (!util_path_is_dir("%s/vipa", path)) return; if (get_qethconf("vipa", if_name, &argz, &argz_len)) util_rec_set_argz(rec, attr_name, argz, argz_len); } free(argz); } /* * Set devices attribute for proc format output: '//' */ static void set_devices_fld(struct util_rec *rec, const char *path) { char buf[3*MAX_ID_LENGTH + 3] = ""; char *link; int i; for (i = 0; i < 3; i++) { link = misc_link_target("%s/cdev%d", path, i); /* File name is a link */ if (i == 2 && link) { strcat(buf, basename(link)); } else { if (link) strcat(strcat(buf, basename(link)), "/"); else strcat(buf, "/"); } free(link); } util_rec_set(rec, "devices", buf); } /* * Check if the attribute should be skipped for layer2 device */ static bool not_layer2_attr(const char *attr_name) { /* Layer3 specific attributes */ static const char *layer3_vec[] = { "route4", "route6", "large_send", "fake_ll", "fake_broadcast", "checksumming", "hsuid", "sniffer" }; return misc_str_in_list(attr_name, layer3_vec, ARRAY_SIZE(layer3_vec)); } /* * Collect and print attributes for qeth-based device in sepcified format */ static void print_device(struct util_rec *rec, const char *device_id) { char *path, *path_net, *if_name; unsigned long int layer2 = 0; struct util_rec_fld *fld; const char *attr_name; char buf[PAGE_SIZE]; path = util_path_sysfs("bus/ccwgroup/drivers/qeth/%s", device_id); /* Process if_name attribute */ if (util_file_read_line(buf, sizeof(buf), "%s/if_name", path) == 0) if_name = util_strdup(buf); else if_name = util_strdup(""); util_rec_set(rec, "if_name", if_name); /* Read layer2 attribute */ path_net = util_path_sysfs("class/net"); util_file_read_ul(&layer2, 10, "%s/%s/device/layer2", path_net, if_name); free(path_net); /* Iterate over each rec field */ util_rec_iterate(rec, fld) { attr_name = util_rec_fld_get_key(fld); /* Skip layer3 attributes for layer2 device in normal format output */ if (layer2 == 1 && !cmd.proc_format && not_layer2_attr(attr_name)) continue; /* Skip if_name attribute(already processed) */ if (strcmp(attr_name, "if_name") == 0) continue; /* Process 'devices' field for proc format output */ if (strcmp(attr_name, "devices") == 0 && cmd.proc_format) { set_devices_fld(rec, path); continue; } /* Process ipa/parp/vipa attributes */ if (strcmp(attr_name, "ipa") == 0 || strcmp(attr_name, "vipa") == 0 || strcmp(attr_name, "parp") == 0) { set_ipa_vipa_parp(rec, attr_name, path, if_name); continue; } /* Process other sysfs attributes */ process_sysfs_attribute(rec, path, attr_name, if_name); } free(if_name); free(path); /* Print the record */ if (!cmd.proc_format) { /* Check if card_type attribute needs to be modified */ update_card_type(rec); /* Print record header for each device in normal format output */ util_rec_print_hdr(rec); } util_rec_print(rec); } /* * Setup record with the fields needed for output in wide form */ static void setup_rec_wide(struct util_rec *rec) { util_rec_def(rec, "devices", UTIL_REC_ALIGN_LEFT, 26, "devices"); util_rec_def(rec, "chpid", UTIL_REC_ALIGN_LEFT, 5, "CHPID"); util_rec_def(rec, "if_name", UTIL_REC_ALIGN_LEFT, 16, "interface"); util_rec_def(rec, "card_type", UTIL_REC_ALIGN_LEFT, 14, "cardtype"); util_rec_def(rec, "portno", UTIL_REC_ALIGN_LEFT, 4, "port"); util_rec_def(rec, "checksumming", UTIL_REC_ALIGN_LEFT, 6, "chksum"); util_rec_def(rec, "priority_queueing", UTIL_REC_ALIGN_LEFT, 10, "prio-q'ing"); util_rec_def(rec, "route4", UTIL_REC_ALIGN_LEFT, 4, "rtr4"); util_rec_def(rec, "route6", UTIL_REC_ALIGN_LEFT, 4, "rtr6"); util_rec_def(rec, "layer2", UTIL_REC_ALIGN_LEFT, 5, "lay'2"); util_rec_def(rec, "buffer_count", UTIL_REC_ALIGN_LEFT, 5, "cnt"); } /* * Setup record for output in long form */ static void setup_rec_long(struct util_rec *rec) { util_rec_def(rec, "if_name", UTIL_REC_ALIGN_LEFT, 0, "Device name"); util_rec_def(rec, "card_type", UTIL_REC_ALIGN_LEFT, 0, "card_type"); util_rec_def(rec, "cdev0", UTIL_REC_ALIGN_LEFT, 0, "cdev0"); util_rec_def(rec, "cdev1", UTIL_REC_ALIGN_LEFT, 0, "cdev1"); util_rec_def(rec, "cdev2", UTIL_REC_ALIGN_LEFT, 0, "cdev2"); util_rec_def(rec, "chpid", UTIL_REC_ALIGN_LEFT, 0, "chpid"); util_rec_def(rec, "online", UTIL_REC_ALIGN_LEFT, 0, "online"); util_rec_def(rec, "portname", UTIL_REC_ALIGN_LEFT, 0, "portname"); util_rec_def(rec, "portno", UTIL_REC_ALIGN_LEFT, 0, "portno"); util_rec_def(rec, "route4", UTIL_REC_ALIGN_LEFT, 0, "route4"); util_rec_def(rec, "route6", UTIL_REC_ALIGN_LEFT, 0, "route6"); util_rec_def(rec, "checksumming", UTIL_REC_ALIGN_LEFT, 0, "checksumming"); util_rec_def(rec, "state", UTIL_REC_ALIGN_LEFT, 0, "state"); util_rec_def(rec, "priority_queueing", UTIL_REC_ALIGN_LEFT, 0, "priority_queueing"); util_rec_def(rec, "detach_state", UTIL_REC_ALIGN_LEFT, 0, "detach_state"); util_rec_def(rec, "fake_ll", UTIL_REC_ALIGN_LEFT, 0, "fake_ll"); util_rec_def(rec, "fake_broadcast", UTIL_REC_ALIGN_LEFT, 0, "fake_broadcast"); util_rec_def(rec, "buffer_count", UTIL_REC_ALIGN_LEFT, 0, "buffer_count"); util_rec_def(rec, "add_hhlen", UTIL_REC_ALIGN_LEFT, 0, "add_hhlen"); util_rec_def(rec, "layer2", UTIL_REC_ALIGN_LEFT, 0, "layer2"); util_rec_def(rec, "large_send", UTIL_REC_ALIGN_LEFT, 0, "large_send"); util_rec_def(rec, "isolation", UTIL_REC_ALIGN_LEFT, 0, "isolation"); util_rec_def(rec, "hsuid", UTIL_REC_ALIGN_LEFT, 0, "hsuid"); util_rec_def(rec, "sniffer", UTIL_REC_ALIGN_LEFT, 0, "sniffer"); util_rec_def(rec, "bridge_role", UTIL_REC_ALIGN_LEFT, 0, "bridge_role"); util_rec_def(rec, "bridge_state", UTIL_REC_ALIGN_LEFT, 0, "bridge_state"); util_rec_def(rec, "bridge_hostnotify", UTIL_REC_ALIGN_LEFT, 0, "bridge_hostnotify"); util_rec_def(rec, "bridge_reflect_promisc", UTIL_REC_ALIGN_LEFT, 0, "bridge_reflect_promisc"); util_rec_def(rec, "switch_attrs", UTIL_REC_ALIGN_LEFT, 0, "switch_attrs"); util_rec_def(rec, "ipa", UTIL_REC_ALIGN_LEFT, 0, "ipa"); util_rec_def(rec, "vipa", UTIL_REC_ALIGN_LEFT, 0, "vipa"); util_rec_def(rec, "parp", UTIL_REC_ALIGN_LEFT, 0, "parp"); util_rec_def(rec, "vnicc/bridge_invisible", UTIL_REC_ALIGN_LEFT, 0, "vnicc/bridge_invisible"); util_rec_def(rec, "vnicc/flooding", UTIL_REC_ALIGN_LEFT, 0, "vnicc/flooding"); util_rec_def(rec, "vnicc/learning", UTIL_REC_ALIGN_LEFT, 0, "vnicc/learning"); util_rec_def(rec, "vnicc/learning_timeout", UTIL_REC_ALIGN_LEFT, 0, "vnicc/learning_timeout"); util_rec_def(rec, "vnicc/mcast_flooding", UTIL_REC_ALIGN_LEFT, 0, "vnicc/mcast_flooding"); util_rec_def(rec, "vnicc/rx_bcast", UTIL_REC_ALIGN_LEFT, 0, "vnicc/rx_bcast"); util_rec_def(rec, "vnicc/takeover_learning", UTIL_REC_ALIGN_LEFT, 0, "vnicc/takeover_learning"); util_rec_def(rec, "vnicc/takeover_setvmac", UTIL_REC_ALIGN_LEFT, 0, "vnicc/takeover_setvmac"); } /* * Setup the record according to the desired output format */ static struct util_rec *setup_rec() { struct util_rec *rec; if (cmd.proc_format) { rec = util_rec_new_wide("-"); setup_rec_wide(rec); } else { rec = util_rec_new_long("-", ":", "if_name", 32, 40); setup_rec_long(rec); } return rec; } /* * Entry point */ int main(int argc, char *argv[]) { char device[MAX_ID_LENGTH]; struct dirent **de_vec; struct util_rec *rec; int i, c = 0, count; char *path, *link; util_prg_init(&prg); util_opt_init(opt_vec, NULL); while (c != -1) { c = util_opt_getopt_long(argc, argv); if (c == -1) break; switch (c) { case 'h': util_prg_print_help(); util_opt_print_help(); return EXIT_SUCCESS; case 'v': util_prg_print_version(); return EXIT_SUCCESS; case 'p': cmd.proc_format = true; continue; default: util_opt_print_parse_error(c, argv); return EXIT_FAILURE; } } /* Error if more than 1 argument specified */ if (argc > optind + 1) { warnx("Too many arguments"); util_prg_print_parse_error(); return EXIT_FAILURE; } /* Create a proper record structure */ rec = setup_rec(); /* For proc output format print record header only once */ if (cmd.proc_format) util_rec_print_hdr(rec); if (argc > optind) { /* Single argument specified (interface name), get related device_id */ path = util_path_sysfs("class/net/"); link = misc_link_target("%s/%s/device/cdev0", path, argv[optind]); free(path); if (link) { /* Interface present */ snprintf(device, sizeof(device), "%s", basename(link)); free(link); print_device(rec, device); free(rec); } else { errx(EXIT_FAILURE, "No such device: %s", argv[optind]); } } else { /* No optional arguments specified, process all available devices */ path = util_path_sysfs("bus/ccwgroup/drivers/qeth/"); count = util_scandir(&de_vec, alphasort, path, "%s", ID_FORMAT); free(path); for (i = 0; i < count; i++) { /* Check if a symbolic link */ if (de_vec[i]->d_type != DT_LNK) continue; if (i > 0) rec = setup_rec(); print_device(rec, de_vec[i]->d_name); free(rec); } util_scandir_free(de_vec, count); } return EXIT_SUCCESS; }