diff --git a/.gitignore b/.gitignore index 03cfe15..54feeec 100644 --- a/.gitignore +++ b/.gitignore @@ -12,4 +12,5 @@ ifupdown ifquery ifup ifdown +ifctrstat *.lock diff --git a/Makefile b/Makefile index 91aee84..8ec124a 100644 --- a/Makefile +++ b/Makefile @@ -62,6 +62,13 @@ MULTICALL_${CONFIG_IFQUERY}_OBJ += ${IFQUERY_SRC:.c=.o} CMDS_${CONFIG_IFQUERY} += ifquery CPPFLAGS_${CONFIG_IFQUERY} += -DCONFIG_IFQUERY +# enable ifctrstat applet (+1 KB) +CONFIG_IFCTRSTAT ?= Y +IFCTRSTAT_SRC = cmd/ifctrstat.c cmd/ifctrstat-${LAYOUT}.c +MULTICALL_${CONFIG_IFCTRSTAT}_OBJ += ${IFCTRSTAT_SRC:.c=.o} +CMDS_${CONFIG_IFCTRSTAT} += ifctrstat +CPPFLAGS_${CONFIG_IFCTRSTAT} += -DCONFIG_IFCTRSTAT + MULTICALL_OBJ += ${MULTICALL_Y_OBJ} CMDS += ${CMDS_Y} CPPFLAGS += ${CPPFLAGS_Y} diff --git a/cmd/ifctrstat-linux.c b/cmd/ifctrstat-linux.c new file mode 100644 index 0000000..e7dcfa3 --- /dev/null +++ b/cmd/ifctrstat-linux.c @@ -0,0 +1,96 @@ +/* + * cmd/ifctrstat-linux.c + * Purpose: Implement ifctrstat system-specific routines for Linux + * + * Copyright (c) 2020 Adélie Software in the Public Benefit, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include +#include +#include +#include +#include "multicall.h" + +struct counter_desc { + const char *name; + const void *data; +} avail_counters[] = { + {"rx.discard", "rx_dropped"}, + {"rx.errors", "rx_errors"}, + {"rx.octets", "rx_bytes"}, + {"rx.packets", "rx_packets"}, + {"tx.discard", "tx_dropped"}, + {"tx.errors", "tx_errors"}, + {"tx.octets", "tx_bytes"}, + {"tx.packets", "tx_packets"} +}; + +size_t avail_counters_count = ARRAY_SIZE(avail_counters); + +static int +counter_compare(const void *key, const void *candidate) +{ + return strcasecmp((const char *)key, ((struct counter_desc *)candidate)->name); +} + +char * +read_counter(const char *interface, const char *counter) +{ + FILE *fp; + const char *path; + char full_path[PATH_MAX]; + char buffer[1024]; + size_t in_count; + struct counter_desc *ctrdata; + + errno = 0; + + ctrdata = bsearch(counter, avail_counters, avail_counters_count, sizeof(struct counter_desc), counter_compare); + if (ctrdata) { + path = (const char *)ctrdata->data; + } else { + errno = ENOSYS; + return NULL; + } + + if (snprintf(full_path, PATH_MAX, "/sys/class/net/%s/statistics/%s", interface, path) > PATH_MAX) + { + errno = ENOMEM; + return NULL; + } + + fp = fopen(full_path, "r"); + if (!fp) + { + return NULL; + } + + in_count = fread(buffer, 1, sizeof(buffer), fp); + + if (in_count == sizeof(buffer)) + { + errno = ENOMEM; + fclose(fp); + return NULL; + } + + if (ferror(fp)) + { + return NULL; + } + + fclose(fp); + + /* take away the \n, we add our own */ + buffer[in_count - 1] = '\0'; + + return strdup(buffer); +} diff --git a/cmd/ifctrstat.c b/cmd/ifctrstat.c new file mode 100644 index 0000000..9c12231 --- /dev/null +++ b/cmd/ifctrstat.c @@ -0,0 +1,161 @@ +/* + * cmd/ifctrstat.c + * Purpose: Display statistics about interfaces on the system + * + * Copyright (c) 2020 Adélie Software in the Public Benefit, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * This software is provided 'as is' and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising + * from the use of this software. + */ + +#include +#include +#include +#include +#include +#include "libifupdown/libifupdown.h" +#include "cmd/multicall.h" + +extern struct counter_desc { const char *name; const void *data; } avail_counters[]; +extern int avail_counters_count; + +extern const char *read_counter(const char *interface, const char *counter); + +static bool show_label = true; + +static bool +counter_is_valid(const char *candidate) +{ + for (int i = 0; i < avail_counters_count; i++) + { + if (strcasecmp(avail_counters[i].name, candidate) == 0) + return true; + } + + return false; +} + +static void +print_counter(const char *iface, const char *name, const char *value) +{ + if (show_label) + fprintf(stdout, "%s: %s\n", name, value); + else + fprintf(stdout, "%s\n", value); +} + +static int +print_all_counters(const char *iface) +{ + int code = EXIT_SUCCESS; + const char *res; + + for (int i = 0; i < avail_counters_count; i++) { + const char *ctr = avail_counters[i].name; + + res = read_counter(iface, ctr); + if (!res) + { + fprintf(stderr, "%s: could not determine value of %s for interface %s: %s\n", argv0, ctr, iface, strerror(errno)); + code = EXIT_FAILURE; + } else { + print_counter(iface, ctr, res); + } + } + + return code; +} + +void +ifctrstat_list_counters(int short_opt, const struct if_option *opt, const char *opt_arg, const struct if_applet *applet) +{ + (void) short_opt; + (void) opt; + (void) opt_arg; + (void) applet; + + for (int i = 0; i < avail_counters_count; i++) + { + fprintf(stdout, "%s\n", avail_counters[i].name); + } + + exit(EXIT_SUCCESS); +} + +void +ifctrstat_set_nolabel(int short_opt, const struct if_option *opt, const char *opt_arg, const struct if_applet *applet) +{ + (void) short_opt; + (void) opt; + (void) opt_arg; + (void) applet; + + show_label = false; +} + +int +ifctrstat_main(int argc, char *argv[]) +{ + if (optind >= argc) + generic_usage(self_applet, EXIT_FAILURE); + + int idx = optind; + if (argc - idx == 0) + { + fprintf(stderr, "%s: interface required\n", + argv0); + return EXIT_FAILURE; + } + + const char *iface = argv[idx++]; + + if (argc - idx == 0) + { + return print_all_counters(iface); + } + + for (; idx < argc; idx++) + { + if (!counter_is_valid(argv[idx])) + { + fprintf(stderr, "%s: counter %s is not valid or not available\n", argv0, argv[idx]); + return EXIT_FAILURE; + } + + errno = 0; + const char *res = read_counter(iface, argv[idx]); + if (!res) + { + fprintf(stderr, "%s: could not determine value of %s for interface %s: %s\n", argv0, argv[idx], iface, strerror(errno)); + return EXIT_FAILURE; + } + + print_counter(iface, argv[idx], res); + } + + return EXIT_SUCCESS; +} + +static struct if_option local_options[] = { + {'L', "list", NULL, "List available counters", false, ifctrstat_list_counters}, + {'n', "no-label", NULL, "Print value without counter label", false, ifctrstat_set_nolabel} +}; + +static struct if_option_group local_option_group = { + .desc = "Program-specific options", + .group_size = ARRAY_SIZE(local_options), + .group = local_options +}; + +struct if_applet ifctrstat_applet = { + .name = "ifctrstat", + .desc = "Display statistics about an interface", + .main = ifctrstat_main, + .usage = "ifctrstat [options] \n ifctrstat [options] --list\n", + .groups = { &global_option_group, &local_option_group, NULL } +}; diff --git a/cmd/multicall.c b/cmd/multicall.c index 8f18f81..0c041ad 100644 --- a/cmd/multicall.c +++ b/cmd/multicall.c @@ -32,10 +32,17 @@ extern struct if_applet ifup_applet; extern struct if_applet ifdown_applet; #endif +#ifdef CONFIG_IFCTRSTAT +extern struct if_applet ifctrstat_applet; +#endif + struct if_applet ifupdown_applet; const struct if_applet *self_applet = NULL; struct if_applet *applet_table[] = { +#ifdef CONFIG_IFCTRSTAT + &ifctrstat_applet, +#endif #ifdef CONFIG_IFUPDOWN &ifdown_applet, #endif