Files
s390-tools/zconf/qeth/lsqeth.c
Szabina Korbai f70991ab1e lsqeth: Implement zsh and bash autocompletion
Add generation of shell autocompletion scripts.

Acked-by: Steffen Eiden <seiden@linux.ibm.com>
Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Szabina Korbai <szkorbai@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2026-04-30 08:51:04 +02:00

649 lines
18 KiB
C

/**
* 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 <linux/ethtool.h>
#include <linux/sockios.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <argz.h>
#include <dirent.h>
#include <fcntl.h>
#include <libgen.h>
#include <limits.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#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 "lsqeth_cli.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
}
};
/*
* Exchange content of field value via simple translation map
*
* If the attribute is required for translation (attribute 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: '<cdev0>/<cdev1>/<cdev2>'
*/
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 specified 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;
}