brctl: add support for showstp command

function                                             old     new   delta
brctl_main                                           974    2339   +1365
show_bridge_timer                                      -      93     +93
static.state_names                                     -      48     +48
printf_xstrtou                                         -      26     +26
packed_usage                                       33243   33253     +10
show_bridge                                          333     323     -10
------------------------------------------------------------------------------
(add/remove: 3/0 grow/shrink: 2/1 up/down: 1542/-10)         Total: 1532 bytes
   text	   data	    bss	    dec	    hex	filename
 999868	    551	   5612	1006031	  f59cf	busybox_old
1002309	    551	   5612	1008472	  f6358	busybox_unstripped

Signed-off-by: Martin Lewis <martin.lewis.x84@gmail.com>
Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
diff --git a/networking/brctl.c b/networking/brctl.c
index ba5b622..e15710b 100644
--- a/networking/brctl.c
+++ b/networking/brctl.c
@@ -54,6 +54,7 @@
 //usage:     "\n	delif BRIDGE IFACE	Delete IFACE from BRIDGE"
 //usage:	IF_FEATURE_BRCTL_FANCY(
 //usage:     "\n	stp BRIDGE 1/yes/on|0/no/off	STP on/off"
+//usage:     "\n	showstp	BRIDGE			Show stp info"
 //usage:     "\n	setageing BRIDGE SECONDS	Set ageing time"
 //usage:     "\n	setfd BRIDGE SECONDS		Set bridge forward delay"
 //usage:     "\n	sethello BRIDGE SECONDS		Set hello time"
@@ -65,7 +66,7 @@
 //usage:	)
 // Not yet implemented:
 //			hairpin BRIDGE IFACE on|off	Hairpin on/off
-//			showstp	BRIDGE			Show stp info
+
 
 #include "libbb.h"
 #include "common_bufsiz.h"
@@ -146,8 +147,7 @@
 
 	if (need_hdr)
 		puts("bridge name\tbridge id\t\tSTP enabled\tinterfaces");
-	printf("%s\t\t", name);
-	printf("%s\t", filedata);
+	printf("%s\t\t%s\t", name, filedata);
 
 	strcpy(sfx, "stp_state");
 	read_file(pathbuf);
@@ -279,6 +279,212 @@
 
 	free(fdb);
 }
+
+static void show_bridge_timer(const char *msg)
+{
+	unsigned long long tvmsec = 10 * xstrtoull(filedata, 0);
+	unsigned tv_sec = tvmsec / 1000;
+	unsigned tv_msec = tvmsec % 1000;
+	printf("%s%4u.%.2u", msg, tv_sec, tv_msec / 10);
+}
+
+static const char *show_bridge_state(unsigned state)
+{
+	/* See linux/if_bridge.h, BR_STATE_ constants */
+	static const char state_names[] =
+		"disabled\0"	//BR_STATE_DISABLED   0
+		"listening\0"   //BR_STATE_LISTENING  1
+		"learning\0"    //BR_STATE_LEARNING   2
+		"forwarding\0"  //BR_STATE_FORWARDING 3
+		"blocking"      //BR_STATE_BLOCKING   4
+	;
+	if (state < 5)
+		return nth_string(state_names, state);
+	return utoa(state);
+}
+
+static void printf_xstrtou(const char *fmt)
+{
+	printf(fmt, xstrtou(filedata, 0));
+}
+
+static void show_bridge_port(const char *name)
+{
+	char pathbuf[IFNAMSIZ + sizeof("/brport/forward_delay_timer") + 32];
+	char *sfx;
+
+#if IFNAMSIZ == 16
+	sfx = pathbuf + sprintf(pathbuf, "%.16s/brport/", name);
+#else
+	sfx = pathbuf + sprintf(pathbuf, "%.*s/brport/", (int)IFNAMSIZ, name);
+#endif
+
+	strcpy(sfx, "port_no");
+	read_file(pathbuf);
+	printf("%s (%u)\n", name, xstrtou(filedata, 0));
+
+	strcpy(sfx + 5, "id"); // "port_id"
+	read_file(pathbuf);
+	printf_xstrtou(" port id\t\t%.4x");
+
+	strcpy(sfx, "state");
+	read_file(pathbuf);
+	printf("\t\t\tstate\t\t%15s\n", show_bridge_state(xstrtou(filedata, 0)));
+
+	strcpy(sfx, "designated_root");
+	read_file(pathbuf);
+	printf(" designated root\t%s", filedata);
+
+	strcpy(sfx, "path_cost");
+	read_file(pathbuf);
+	printf_xstrtou("\tpath cost\t\t%4u\n");
+
+	strcpy(sfx, "designated_bridge");
+	read_file(pathbuf);
+	printf(" designated bridge\t%s", filedata);
+
+	strcpy(sfx, "message_age_timer");
+	read_file(pathbuf);
+	show_bridge_timer("\tmessage age timer\t");
+
+	strcpy(sfx, "designated_port");
+	read_file(pathbuf);
+	printf_xstrtou("\n designated port\t%.4x");
+
+	strcpy(sfx, "forward_delay_timer");
+	read_file(pathbuf);
+	show_bridge_timer("\t\t\tforward delay timer\t");
+
+	strcpy(sfx, "designated_cost");
+	read_file(pathbuf);
+	printf_xstrtou("\n designated cost\t%4u");
+
+	strcpy(sfx, "hold_timer");
+	read_file(pathbuf);
+	show_bridge_timer("\t\t\thold timer\t\t");
+
+	printf("\n flags\t\t\t");
+
+	strcpy(sfx, "config_pending");
+	read_file(pathbuf);
+	if (!LONE_CHAR(filedata, '0'))
+		printf("CONFIG_PENDING ");
+
+	strcpy(sfx, "change_ack");
+	read_file(pathbuf);
+	if (!LONE_CHAR(filedata, '0'))
+		printf("TOPOLOGY_CHANGE_ACK ");
+
+	strcpy(sfx, "hairpin_mode");
+	read_file(pathbuf);
+	if (!LONE_CHAR(filedata, '0'))
+		printf_xstrtou("\n hairpin mode\t\t%4u");
+
+	printf("\n\n");
+}
+
+static void show_bridge_ports(const char *name)
+{
+	DIR *ifaces;
+	struct dirent *ent;
+	char pathbuf[IFNAMSIZ + sizeof("/brif") + 8];
+
+#if IFNAMSIZ == 16
+	sprintf(pathbuf, "%.16s/brif", name);
+#else
+	sprintf(pathbuf, "%.*s/brif", (int)IFNAMSIZ, name);
+#endif
+	ifaces = opendir(pathbuf);
+	if (ifaces) {
+		while ((ent = readdir(ifaces)) != NULL) {
+			if (DOT_OR_DOTDOT(ent->d_name))
+				continue; /* . or .. */
+			show_bridge_port(ent->d_name);
+		}
+		closedir(ifaces);
+	}
+}
+
+static void show_bridge_stp(const char *name)
+{
+	char pathbuf[IFNAMSIZ + sizeof("/bridge/topology_change_timer") + 32];
+	char *sfx;
+
+#if IFNAMSIZ == 16
+	sfx = pathbuf + sprintf(pathbuf, "%.16s/bridge/", name);
+#else
+	sfx = pathbuf + sprintf(pathbuf, "%.*s/bridge/", (int)IFNAMSIZ, name);
+#endif
+
+	strcpy(sfx, "bridge_id");
+	if (read_file(pathbuf) < 0)
+		bb_error_msg_and_die("bridge %s does not exist", name);
+
+	printf("%s\n"
+		" bridge id\t\t%s", name, filedata);
+
+	strcpy(sfx, "root_id");
+	read_file(pathbuf);
+	printf("\n designated root\t%s", filedata);
+
+	strcpy(sfx + 5, "port"); // "root_port"
+	read_file(pathbuf);
+	printf_xstrtou("\n root port\t\t%4u\t\t\t");
+
+	strcpy(sfx + 6, "ath_cost"); // "root_path_cost"
+	read_file(pathbuf);
+	printf_xstrtou("path cost\t\t%4u\n");
+
+	strcpy(sfx, "max_age");
+	read_file(pathbuf);
+	show_bridge_timer(" max age\t\t");
+	show_bridge_timer("\t\t\tbridge max age\t\t");
+
+	strcpy(sfx, "hello_time");
+	read_file(pathbuf);
+	show_bridge_timer("\n hello time\t\t");
+	show_bridge_timer("\t\t\tbridge hello time\t");
+
+	strcpy(sfx, "forward_delay");
+	read_file(pathbuf);
+	show_bridge_timer("\n forward delay\t\t");
+	show_bridge_timer("\t\t\tbridge forward delay\t");
+
+	strcpy(sfx, "ageing_time");
+	read_file(pathbuf);
+	show_bridge_timer("\n ageing time\t\t");
+
+	strcpy(sfx, "hello_timer");
+	read_file(pathbuf);
+	show_bridge_timer("\n hello timer\t\t");
+
+	strcpy(sfx, "tcn_timer");
+	read_file(pathbuf);
+	show_bridge_timer("\t\t\ttcn timer\t\t");
+
+	strcpy(sfx, "topology_change_timer");
+	read_file(pathbuf);
+	show_bridge_timer("\n topology change timer\t");
+
+	strcpy(sfx, "gc_timer");
+	read_file(pathbuf);
+	show_bridge_timer("\t\t\tgc timer\t\t");
+
+	printf("\n flags\t\t\t");
+
+	strcpy(sfx, "topology_change");
+	read_file(pathbuf);
+	if (!LONE_CHAR(filedata, '0'))
+		printf("TOPOLOGY_CHANGE ");
+
+	strcpy(sfx, "topology_change_detected");
+	read_file(pathbuf);
+	if (!LONE_CHAR(filedata, '0'))
+		printf("TOPOLOGY_CHANGE_DETECTED ");
+	printf("\n\n\n");
+
+	show_bridge_ports(name);
+}
 #endif
 
 int brctl_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
@@ -288,6 +494,7 @@
 		"addbr\0" "delbr\0" "addif\0" "delif\0"
 	IF_FEATURE_BRCTL_FANCY(
 		"stp\0"
+		"showstp\0"
 		"setageing\0" "setfd\0" "sethello\0" "setmaxage\0"
 		"setpathcost\0" "setportprio\0"
 		"setbridgeprio\0"
@@ -297,6 +504,7 @@
 	enum { ARG_addbr = 0, ARG_delbr, ARG_addif, ARG_delif
 		IF_FEATURE_BRCTL_FANCY(,
 			ARG_stp,
+			ARG_showstp,
 			ARG_setageing, ARG_setfd, ARG_sethello, ARG_setmaxage,
 			ARG_setpathcost, ARG_setportprio,
 			ARG_setbridgeprio,
@@ -388,6 +596,10 @@
 			show_bridge_macs(br);
 			return EXIT_SUCCESS;
 		}
+		if (key == ARG_showstp) {
+			show_bridge_stp(br);
+			return EXIT_SUCCESS;
+		}
 
 		if (!*argv) /* all but 'addbr/delbr' need at least two arguments */
 			bb_show_usage();