git: 31749859525b - main - netstat(1): '-w': Banners to appear before a new statistics line, not after

From: Olivier Certner <olce_at_FreeBSD.org>
Date: Tue, 07 Apr 2026 10:23:18 UTC
The branch main has been updated by olce:

URL: https://cgit.FreeBSD.org/src/commit/?id=31749859525b2b79634dc7c066c3563be5b1e3fd

commit 31749859525b2b79634dc7c066c3563be5b1e3fd
Author:     Olivier Certner <olce@FreeBSD.org>
AuthorDate: 2026-04-02 19:13:02 +0000
Commit:     Olivier Certner <olce@FreeBSD.org>
CommitDate: 2026-04-07 09:23:09 +0000

    netstat(1): '-w': Banners to appear before a new statistics line, not after
    
    Recurring banners except the first are printed just after the latest
    interval's statistics line, giving the false impression that the latter
    are omitted.  It is also better to print a new banner only if it is
    going to be followed by a new line of statistics, in case netstat(1) is
    interrupted or we have reached the number of iterations specified by
    '-q'.
    
    Fix this by pushing printing these banners inside the loop producing
    statistics lines, after having waited for the next interval.
    
    The first banner is printed before the loop, as we want it to be printed
    immediately at launch, even if at this point we do not have statistics
    to display (we have to wait for an interval to compute these, as they
    are based on a difference).
    
    While here, remove the 'goto' spaghetti by putting banner printing into
    its own private function and using a proper infinite loop in
    sidewaysintpr().
    
    While here, document the why of the 21 statistics line span between two
    banners.
    
    While here, check for the number of output lines of statistics once such
    a line has effectively been printed.  This allows to remove the internal
    incrementation performed when reading '-w''s argument, which was a hack
    to compensate the misplaced check.
    
    While here, in the manual page, simplify the description of the '-w'
    mode and mention that passing 0 to '-q' means "no count limit".
    
    Reviewed by:    glebius
    Fixes:          84c1edcbad7d ("Rewrite netstat/if.c to use ...")
    Fixes:          bf10ffe1d3a9 ("Add a new option, -q howmany, ...")
    MFC after:      1 week
    Sponsored by:   The FreeBSD Foundation
    Differential Revision:  https://reviews.freebsd.org/D56227
---
 usr.bin/netstat/if.c      | 146 +++++++++++++++++++++++++++-------------------
 usr.bin/netstat/main.c    |   2 -
 usr.bin/netstat/netstat.1 |  22 +++----
 3 files changed, 93 insertions(+), 77 deletions(-)

diff --git a/usr.bin/netstat/if.c b/usr.bin/netstat/if.c
index 7ee03eb3689b..637aa4738d40 100644
--- a/usr.bin/netstat/if.c
+++ b/usr.bin/netstat/if.c
@@ -396,7 +396,7 @@ intpr(void (*pfunc)(char *), int af)
 	u_int npkt_len = 8, nbyte_len = 10, nerr_len = 5;
 
 	if (interval)
-		return sidewaysintpr();
+		return (sidewaysintpr());
 
 	if (getifaddrs(&ifap) != 0)
 		xo_err(EX_OSERR, "getifaddrs");
@@ -652,6 +652,21 @@ catchalarm(int signo __unused)
 	signalled = true;
 }
 
+static void
+running_stats_banner(void)
+{
+	xo_emit("{T:/%17s} {T:/%14s} {T:/%16s}\n", "input",
+	    interface != NULL ? interface : "(Total)", "output");
+	xo_emit("{T:/%10s} {T:/%5s} {T:/%5s} {T:/%10s} {T:/%10s} {T:/%5s} "
+	    "{T:/%10s} {T:/%5s}",
+	    "packets", "errs", "idrops", "bytes", "packets", "errs", "bytes",
+	    "colls");
+	if (dflag)
+		xo_emit(" {T:/%5.5s}", "drops");
+	xo_emit("\n");
+	xo_flush();
+}
+
 /*
  * Print a running summary of interface statistics.
  * Repeat display every interval seconds, showing statistics
@@ -675,71 +690,82 @@ sidewaysintpr(void)
 	interval_it.it_interval.tv_usec = 0;
 	interval_it.it_value = interval_it.it_interval;
 	setitimer(ITIMER_REAL, &interval_it, NULL);
+
 	xo_open_list("interface-statistics");
 
-banner:
-	xo_emit("{T:/%17s} {T:/%14s} {T:/%16s}\n", "input",
-	    interface != NULL ? interface : "(Total)", "output");
-	xo_emit("{T:/%10s} {T:/%5s} {T:/%5s} {T:/%10s} {T:/%10s} {T:/%5s} "
-	    "{T:/%10s} {T:/%5s}",
-	    "packets", "errs", "idrops", "bytes", "packets", "errs", "bytes",
-	    "colls");
-	if (dflag)
-		xo_emit(" {T:/%5.5s}", "drops");
-	xo_emit("\n");
-	xo_flush();
+	/*
+	 * We print the first banner right away, even if we'll have to wait for
+	 * the specified interval to get the first statistics line.  Next
+	 * banners will be printed only when the next statistics line is
+	 * available.
+	 */
+	running_stats_banner();
+	/* Number of written statistics line since latest banner. */
 	line = 0;
 
-loop:
-	if ((noutputs != 0) && (--noutputs == 0)) {
-		xo_close_list("interface-statistics");
-		return;
-	}
-	oldmask = sigblock(sigmask(SIGALRM));
-	while (!signalled)
-		sigpause(0);
-	signalled = false;
-	sigsetmask(oldmask);
-	line++;
-
-	fill_iftot(new);
-
-	xo_open_instance("stats");
-	show_stat("lu", 10, "received-packets",
-	    new->ift_ip - old->ift_ip, 1, 1);
-	show_stat("lu", 5, "received-errors",
-	    new->ift_ie - old->ift_ie, 1, 1);
-	show_stat("lu", 5, "dropped-packets",
-	    new->ift_id - old->ift_id, 1, 1);
-	show_stat("lu", 10, "received-bytes",
-	    new->ift_ib - old->ift_ib, 1, 0);
-	show_stat("lu", 10, "sent-packets",
-	    new->ift_op - old->ift_op, 1, 1);
-	show_stat("lu", 5, "send-errors",
-	    new->ift_oe - old->ift_oe, 1, 1);
-	show_stat("lu", 10, "sent-bytes",
-	    new->ift_ob - old->ift_ob, 1, 0);
-	show_stat("NRSlu", 5, "collisions",
-	    new->ift_co - old->ift_co, 1, 1);
-	if (dflag)
-		show_stat("LSlu", 5, "dropped-packets",
-		    new->ift_od - old->ift_od, 1, 1);
-	xo_close_instance("stats");
-	xo_emit("\n");
-	xo_flush();
+	for (;;) {
+		/* Wait for next interval. */
+		oldmask = sigblock(sigmask(SIGALRM));
+		while (!signalled)
+			sigpause(0);
+		signalled = false;
+		sigsetmask(oldmask);
 
-	if (new == &ift[0]) {
-		new = &ift[1];
-		old = &ift[0];
-	} else {
-		new = &ift[0];
-		old = &ift[1];
-	}
+		fill_iftot(new);
 
-	if (line == 21)
-		goto banner;
-	else
-		goto loop;
+		/*
+		 * We want that, on a 24-line display, when the previous banner
+		 * reaches exactly the top of the screen, a new banner is
+		 * printed before the next statistics line.  This ensures that
+		 * at least one banner is visible on screen at all time
+		 * (provided the screen has at least 24 lines).  Since the
+		 * banner takes 2 lines, and the last screen line is occupied by
+		 * the cursor, it's after having written 21 statistics lines
+		 * that we must output the banner.
+		 */
+		if (line == 21) {
+			running_stats_banner();
+			line = 0;
+		}
+		++line;
+
+		xo_open_instance("stats");
+		show_stat("lu", 10, "received-packets",
+		    new->ift_ip - old->ift_ip, 1, 1);
+		show_stat("lu", 5, "received-errors",
+		    new->ift_ie - old->ift_ie, 1, 1);
+		show_stat("lu", 5, "dropped-packets",
+		    new->ift_id - old->ift_id, 1, 1);
+		show_stat("lu", 10, "received-bytes",
+		    new->ift_ib - old->ift_ib, 1, 0);
+		show_stat("lu", 10, "sent-packets",
+		    new->ift_op - old->ift_op, 1, 1);
+		show_stat("lu", 5, "send-errors",
+		    new->ift_oe - old->ift_oe, 1, 1);
+		show_stat("lu", 10, "sent-bytes",
+		    new->ift_ob - old->ift_ob, 1, 0);
+		show_stat("NRSlu", 5, "collisions",
+		    new->ift_co - old->ift_co, 1, 1);
+		if (dflag)
+			show_stat("LSlu", 5, "dropped-packets",
+			    new->ift_od - old->ift_od, 1, 1);
+		xo_close_instance("stats");
+		xo_emit("\n");
+		xo_flush();
+
+		if ((noutputs != 0) && (--noutputs == 0)) {
+			xo_close_list("interface-statistics");
+			return;
+		}
+
+		if (new == &ift[0]) {
+			new = &ift[1];
+			old = &ift[0];
+		} else {
+			new = &ift[0];
+			old = &ift[1];
+		}
+	}
 
 	/* NOTREACHED */
 }
diff --git a/usr.bin/netstat/main.c b/usr.bin/netstat/main.c
index 79830049948a..d81e7c5e8307 100644
--- a/usr.bin/netstat/main.c
+++ b/usr.bin/netstat/main.c
@@ -389,8 +389,6 @@ main(int argc, char *argv[])
 			break;
 		case 'q':
 			noutputs = atoi(optarg);
-			if (noutputs != 0)
-				noutputs++;
 			break;
 		case 'r':
 			rflag = true;
diff --git a/usr.bin/netstat/netstat.1 b/usr.bin/netstat/netstat.1
index 1931c38a1fad..a726c703752f 100644
--- a/usr.bin/netstat/netstat.1
+++ b/usr.bin/netstat/netstat.1
@@ -25,7 +25,7 @@
 .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 .\" SUCH DAMAGE.
 .\"
-.Dd July 16, 2025
+.Dd April 3, 2026
 .Dt NETSTAT 1
 .Os
 .Sh NAME
@@ -355,23 +355,14 @@ See
 At intervals of
 .Ar wait
 seconds, display the information regarding packet traffic on all
-configured network interfaces or a single
-.Ar interface .
+configured network interfaces, or a single
+.Ar interface
+if
+.Fl I
+is specified.
 .Pp
-When
-.Nm
-is invoked with the
-.Fl w
-option and a
-.Ar wait
-interval argument, it displays a running count of statistics related to
-network interfaces.
 An obsolescent version of this option used a numeric parameter
 with no option, and is currently supported for backward compatibility.
-By default, this display summarizes information for all interfaces.
-Information for a specific interface may be displayed with the
-.Fl I Ar interface
-option.
 .Bl -tag -width indent
 .It Fl I Ar interface
 Only show information regarding
@@ -398,6 +389,7 @@ See
 Exit after
 .Ar howmany
 outputs.
+A value of zero indicates no limit, and is the default.
 .It Fl j Ar jail
 Run inside a jail.
 See