Netdev List
 help / color / mirror / Atom feed
From: Rose Wright <rosesophiewright@gmail.com>
To: Stephen Hemminger <stephen@networkplumber.org>
Cc: netdev@vger.kernel.org, Rose Wright <rosesophiewright@gmail.com>
Subject: [PATCH v2] iproute2: return correct status from help commands across all utilities
Date: Tue, 23 Jun 2026 01:14:54 +0000	[thread overview]
Message-ID: <20260623011454.214244-1-rosesophiewright@gmail.com> (raw)
In-Reply-To: <20260622081637.172a6bb8@phoenix.local>

Currently, help commands ("help", "-h", "-help") in utilities
always return error codes.

This is a bug which breaks piping, grep operations, and scripts
that rely on standard exit codes. The fix parameterizes the usage/help
functions in these utilities to dynamically write to stdout when help
is explicitly requested.

This patch standardizes the behavior of help commands and usage errors
across all utilities: ip, bridge, tc, devlink, dcb, rdma, vdpa, dpll,
genl, tipc, and netshaper.

Netlink socket initialization is bypassed when a help command is used to prevent commands from failing in isolated
test/container environments.

Test added to confirm these changes: testsuite/tests/ip/help.t

Signed-off-by: Rose Wright <rosesophiewright@gmail.com>
---
 bridge/bridge.c           |  22 +++---
 dcb/dcb.c                 |  38 +++++++---
 devlink/devlink.c         |  42 ++++++++---
 dpll/dpll.c               |  31 +++++---
 genl/genl.c               |  14 ++--
 netshaper/netshaper.c     |  52 ++++++++++---
 rdma/rdma.c               |  39 +++++++---
 tc/tc.c                   |  14 ++--
 testsuite/tests/ip/help.t | 151 ++++++++++++++++++++++++++++++++++++++
 tipc/bearer.c             | 142 +++++++++++++++++++----------------
 tipc/bearer.h             |   8 +-
 tipc/cmdl.c               |   9 ++-
 tipc/cmdl.h               |   6 +-
 tipc/link.c               | 108 ++++++++++++++-------------
 tipc/link.h               |   4 +-
 tipc/media.c              |  24 +++---
 tipc/media.h              |   4 +-
 tipc/nametable.c          |   8 +-
 tipc/nametable.h          |   4 +-
 tipc/node.c               |  54 ++++++++------
 tipc/node.h               |   4 +-
 tipc/peer.c               |  26 +++----
 tipc/peer.h               |   4 +-
 tipc/socket.c             |   8 +-
 tipc/socket.h             |   4 +-
 tipc/tipc.c               |  26 ++++---
 vdpa/vdpa.c               |  40 +++++++---
 27 files changed, 598 insertions(+), 288 deletions(-)
 create mode 100755 testsuite/tests/ip/help.t

diff --git a/bridge/bridge.c b/bridge/bridge.c
index d993ba19..7bc08b82 100644
--- a/bridge/bridge.c
+++ b/bridge/bridge.c
@@ -29,23 +29,25 @@ int timestamp;
 static const char *batch_file;
 int force;
 
-static void usage(void) __attribute__((noreturn));
+static void usage(int status) __attribute__((noreturn));
 
-static void usage(void)
+static void usage(int status)
 {
-	fprintf(stderr,
+	FILE *out = status == 0 ? stdout : stderr;
+
+	fprintf(out,
 "Usage: bridge [ OPTIONS ] OBJECT { COMMAND | help }\n"
 "       bridge [ -force ] -batch filename\n"
 "where  OBJECT := { link | fdb | mdb | mst | vlan | vni | monitor }\n"
 "       OPTIONS := { -V[ersion] | -s[tatistics] | -d[etails] |\n"
 "                    -o[neline] | -t[imestamp] | -n[etns] name |\n"
 "                    -com[pressvlans] -c[olor] -p[retty] -j[son] }\n");
-	exit(-1);
+	exit(status);
 }
 
 static int do_help(int argc, char **argv)
 {
-	usage();
+	usage(0);
 }
 
 
@@ -118,7 +120,7 @@ main(int argc, char **argv)
 			opt++;
 
 		if (matches(opt, "-help") == 0) {
-			usage();
+			usage(0);
 		} else if (matches(opt, "-Version") == 0) {
 			printf("bridge utility, %s\n", version);
 			exit(0);
@@ -135,13 +137,13 @@ main(int argc, char **argv)
 			argc--;
 			argv++;
 			if (argc <= 1)
-				usage();
+				usage(-1);
 			if (strcmp(argv[1], "inet") == 0)
 				preferred_family = AF_INET;
 			else if (strcmp(argv[1], "inet6") == 0)
 				preferred_family = AF_INET6;
 			else if (strcmp(argv[1], "help") == 0)
-				usage();
+				usage(0);
 			else
 				invarg("invalid protocol family", argv[1]);
 		} else if (strcmp(opt, "-4") == 0) {
@@ -165,7 +167,7 @@ main(int argc, char **argv)
 			argc--;
 			argv++;
 			if (argc <= 1)
-				usage();
+				usage(-1);
 			batch_file = argv[1];
 		} else {
 			fprintf(stderr,
@@ -192,5 +194,5 @@ main(int argc, char **argv)
 		return do_cmd(argv[1], argc-1, argv+1);
 
 	rtnl_close(&rth);
-	usage();
+	usage(-1);
 }
diff --git a/dcb/dcb.c b/dcb/dcb.c
index fe0a0f04..131005fe 100644
--- a/dcb/dcb.c
+++ b/dcb/dcb.c
@@ -465,9 +465,9 @@ int dcb_cmd_parse_dev(struct dcb *dcb, int argc, char **argv,
 	}
 }
 
-static void dcb_help(void)
+static void dcb_help(FILE *out)
 {
-	fprintf(stderr,
+	fprintf(out,
 		"Usage: dcb [ OPTIONS ] OBJECT { COMMAND | help }\n"
 		"       dcb [ -f | --force ] { -b | --batch } filename [ -n | --netns ] netnsname\n"
 		"where  OBJECT := { app | apptrust | buffer | dcbx | ets | maxrate | pfc | rewr }\n"
@@ -478,8 +478,11 @@ static void dcb_help(void)
 
 static int dcb_cmd(struct dcb *dcb, int argc, char **argv)
 {
-	if (!argc || matches(*argv, "help") == 0) {
-		dcb_help();
+	if (!argc) {
+		dcb_help(stderr);
+		return -EINVAL;
+	} else if (matches(*argv, "help") == 0) {
+		dcb_help(stdout);
 		return 0;
 	} else if (matches(*argv, "app") == 0) {
 		return dcb_cmd_app(dcb, argc - 1, argv + 1);
@@ -533,6 +536,7 @@ int main(int argc, char **argv)
 	const char *batch_file = NULL;
 	bool force = false;
 	struct dcb *dcb;
+	bool need_nl = true;
 	int opt;
 	int err;
 	int ret;
@@ -579,12 +583,12 @@ int main(int argc, char **argv)
 			dcb->use_iec = true;
 			break;
 		case 'h':
-			dcb_help();
+			dcb_help(stdout);
 			ret = EXIT_SUCCESS;
 			goto dcb_free;
 		default:
 			fprintf(stderr, "Unknown option.\n");
-			dcb_help();
+			dcb_help(stderr);
 			ret = EXIT_FAILURE;
 			goto dcb_free;
 		}
@@ -593,10 +597,21 @@ int main(int argc, char **argv)
 	argc -= optind;
 	argv += optind;
 
-	err = dcb_init(dcb);
-	if (err) {
-		ret = EXIT_FAILURE;
-		goto dcb_free;
+	if (argc > 0 && (strcmp(argv[0], "help") == 0 ||
+			 strcmp(argv[0], "-h") == 0 ||
+			 strcmp(argv[0], "--help") == 0))
+		need_nl = false;
+	if (argc > 1 && (strcmp(argv[1], "help") == 0 ||
+			 strcmp(argv[1], "-h") == 0 ||
+			 strcmp(argv[1], "--help") == 0))
+		need_nl = false;
+
+	if (need_nl) {
+		err = dcb_init(dcb);
+		if (err) {
+			ret = EXIT_FAILURE;
+			goto dcb_free;
+		}
 	}
 
 	if (batch_file)
@@ -612,7 +627,8 @@ int main(int argc, char **argv)
 	ret = EXIT_SUCCESS;
 
 dcb_fini:
-	dcb_fini(dcb);
+	if (need_nl)
+		dcb_fini(dcb);
 dcb_free:
 	dcb_free(dcb);
 
diff --git a/devlink/devlink.c b/devlink/devlink.c
index b4deba30..ad64ba1e 100644
--- a/devlink/devlink.c
+++ b/devlink/devlink.c
@@ -10332,9 +10332,9 @@ static int cmd_trap(struct dl *dl)
 	return -ENOENT;
 }
 
-static void help(void)
+static void help(FILE *out)
 {
-	pr_err("Usage: devlink [ OPTIONS ] OBJECT { COMMAND | help }\n"
+	fprintf(out, "Usage: devlink [ OPTIONS ] OBJECT { COMMAND | help }\n"
 	       "       devlink [ -f[orce] ] -b[atch] filename -N[etns] netnsname\n"
 	       "where  OBJECT := { dev | port | lc | sb | monitor | dpipe | resource | region | health | trap }\n"
 	       "       OPTIONS := { -V[ersion] | -n[o-nice-names] | -j[son] | -p[retty] | -v[erbose] -s[tatistics] -[he]x }\n");
@@ -10345,9 +10345,12 @@ static int dl_cmd(struct dl *dl, int argc, char **argv)
 	dl->argc = argc;
 	dl->argv = argv;
 
-	if (dl_argv_match(dl, "help") || dl_no_arg(dl)) {
-		help();
+	if (dl_argv_match(dl, "help")) {
+		help(stdout);
 		return 0;
+	} else if (dl_no_arg(dl)) {
+		help(stderr);
+		return -EINVAL;
 	} else if (dl_argv_match(dl, "dev")) {
 		dl_arg_inc(dl);
 		return cmd_dev(dl);
@@ -10454,6 +10457,7 @@ int main(int argc, char **argv)
 	const char *batch_file = NULL;
 	bool force = false;
 	struct dl *dl;
+	bool need_nl = true;
 	int opt;
 	int err;
 	int ret;
@@ -10464,7 +10468,7 @@ int main(int argc, char **argv)
 		return EXIT_FAILURE;
 	}
 
-	while ((opt = getopt_long(argc, argv, "Vfb:njpvsN:ix",
+	while ((opt = getopt_long(argc, argv, "Vfb:njpvsN:ixh",
 				  long_options, NULL)) >= 0) {
 
 		switch (opt) {
@@ -10505,9 +10509,13 @@ int main(int argc, char **argv)
 		case 'x':
 			dl->hex = true;
 			break;
+		case 'h':
+			help(stdout);
+			ret = EXIT_SUCCESS;
+			goto dl_free;
 		default:
 			pr_err("Unknown option.\n");
-			help();
+			help(stderr);
 			ret = EXIT_FAILURE;
 			goto dl_free;
 		}
@@ -10516,10 +10524,21 @@ int main(int argc, char **argv)
 	argc -= optind;
 	argv += optind;
 
-	err = dl_init(dl);
-	if (err) {
-		ret = EXIT_FAILURE;
-		goto dl_free;
+	if (argc > 0 && (strcmp(argv[0], "help") == 0 ||
+			 strcmp(argv[0], "-h") == 0 ||
+			 strcmp(argv[0], "--help") == 0))
+		need_nl = false;
+	if (argc > 1 && (strcmp(argv[1], "help") == 0 ||
+			 strcmp(argv[1], "-h") == 0 ||
+			 strcmp(argv[1], "--help") == 0))
+		need_nl = false;
+
+	if (need_nl) {
+		err = dl_init(dl);
+		if (err) {
+			ret = EXIT_FAILURE;
+			goto dl_free;
+		}
 	}
 
 	if (batch_file)
@@ -10535,7 +10554,8 @@ int main(int argc, char **argv)
 	ret = EXIT_SUCCESS;
 
 dl_fini:
-	dl_fini(dl);
+	if (need_nl)
+		dl_fini(dl);
 dl_free:
 	dl_free(dl);
 
diff --git a/dpll/dpll.c b/dpll/dpll.c
index 60404e13..e0afb4fe 100644
--- a/dpll/dpll.c
+++ b/dpll/dpll.c
@@ -575,9 +575,9 @@ static void dpll_pr_freq_range(__u64 freq_min, __u64 freq_max)
 	close_json_object();
 }
 
-static void help(void)
+static void help(FILE *out)
 {
-	pr_err("Usage: dpll [ OPTIONS ] OBJECT { COMMAND | help }\n"
+	fprintf(out, "Usage: dpll [ OPTIONS ] OBJECT { COMMAND | help }\n"
 	       "where  OBJECT := { device | pin | monitor }\n"
 	       "       OPTIONS := { -V | --Version | -j | --json | -p | --pretty |\n"
 	       "                    -t | --timestamp | --tshort }\n");
@@ -592,9 +592,12 @@ static int dpll_cmd(struct dpll *dpll, int argc, char **argv)
 	dpll->argc = argc;
 	dpll->argv = argv;
 
-	if (dpll_argv_match(dpll, "help") || dpll_no_arg(dpll)) {
-		help();
+	if (dpll_argv_match(dpll, "help")) {
+		help(stdout);
 		return 0;
+	} else if (dpll_no_arg(dpll)) {
+		help(stderr);
+		return -EINVAL;
 	} else if (dpll_argv_match_inc(dpll, "device")) {
 		return cmd_device(dpll);
 	} else if (dpll_argv_match_inc(dpll, "pin")) {
@@ -646,10 +649,12 @@ int main(int argc, char **argv)
 		{ "pretty", no_argument, NULL, 'p' },
 		{ "timestamp", no_argument, NULL, 't' },
 		{ "tshort", no_argument, NULL, OPT_TSHORT },
+		{ "help", no_argument, NULL, 'h' },
 		{ NULL, 0, NULL, 0 }
 	};
-	const char *opt_short = "Vjpt";
+	const char *opt_short = "Vjpth";
 	struct dpll *dpll;
+	bool need_nl = true;
 	int err, opt, ret;
 
 	dpll = dpll_alloc();
@@ -678,9 +683,13 @@ int main(int argc, char **argv)
 			timestamp = 1;
 			timestamp_short = 1;
 			break;
+		case 'h':
+			help(stdout);
+			ret = EXIT_SUCCESS;
+			goto dpll_free;
 		default:
 			pr_err("Unknown option.\n");
-			help();
+			help(stderr);
 			ret = EXIT_FAILURE;
 			goto dpll_free;
 		}
@@ -700,11 +709,13 @@ int main(int argc, char **argv)
 	}
 
 	/* Skip netlink init for help commands */
-	bool need_nl = true;
-
-	if (argc > 0 && strcmp(argv[0], "help") == 0)
+	if (argc > 0 && (strcmp(argv[0], "help") == 0 ||
+			 strcmp(argv[0], "-h") == 0 ||
+			 strcmp(argv[0], "--help") == 0))
 		need_nl = false;
-	if (argc > 1 && strcmp(argv[1], "help") == 0)
+	if (argc > 1 && (strcmp(argv[1], "help") == 0 ||
+			 strcmp(argv[1], "-h") == 0 ||
+			 strcmp(argv[1], "--help") == 0))
 		need_nl = false;
 
 	if (need_nl) {
diff --git a/genl/genl.c b/genl/genl.c
index b497a3ad..2c304a57 100644
--- a/genl/genl.c
+++ b/genl/genl.c
@@ -90,16 +90,18 @@ noexist:
 	return f;
 }
 
-static void usage(void) __attribute__((noreturn));
+static void usage(int status) __attribute__((noreturn));
 
-static void usage(void)
+static void usage(int status)
 {
-	fprintf(stderr,
+	FILE *out = status == 0 ? stdout : stderr;
+
+	fprintf(out,
 		"Usage: genl [ OPTIONS ] OBJECT [help] }\n"
 		"where  OBJECT := { ctrl etc }\n"
 		"       OPTIONS := { -V[ersion] | -s[tatistics] | -d[etails] | -r[aw] |\n"
 		"                    -j[son] | -p[retty] }\n");
-	exit(-1);
+	exit(status);
 }
 
 int main(int argc, char **argv)
@@ -122,7 +124,7 @@ int main(int argc, char **argv)
 		} else if (matches(argv[1], "-pretty") == 0) {
 			++pretty;
 		} else if (matches(argv[1], "-help") == 0) {
-			usage();
+			usage(0);
 		} else {
 			fprintf(stderr,
 				"Option \"%s\" is unknown, try \"genl -help\".\n",
@@ -146,5 +148,5 @@ int main(int argc, char **argv)
 		return ret;
 	}
 
-	usage();
+	usage(-1);
 }
diff --git a/netshaper/netshaper.c b/netshaper/netshaper.c
index 3b47d43d..f9aeac9b 100644
--- a/netshaper/netshaper.c
+++ b/netshaper/netshaper.c
@@ -26,9 +26,11 @@
 static struct rtnl_handle gen_rth = { .fd = -1 };
 static int genl_family = -1;
 
-static void usage(void)
+static void usage(int status)
 {
-	fprintf(stderr,
+	FILE *out = status == 0 ? stdout : stderr;
+
+	fprintf(out,
 		"Usage: netshaper [ OPTIONS ] { COMMAND | help }\n"
 		"OPTIONS := { -V[ersion] | -c[olor] | -help }\n"
 		"COMMAND := { set | get | delete | group } dev DEVNAME\n"
@@ -216,6 +218,13 @@ static void print_netshaper_attrs(struct nlmsghdr *answer)
 
 static int do_cmd(int argc, char **argv, int cmd)
 {
+	if (argc > 0 && (strcmp(*argv, "help") == 0 ||
+			 strcmp(*argv, "-h") == 0 ||
+			 strcmp(*argv, "--help") == 0)) {
+		usage(0);
+		exit(0);
+	}
+
 	GENL_REQUEST(req, 1024, genl_family, 0, NET_SHAPER_FAMILY_VERSION, cmd,
 		     NLM_F_REQUEST | NLM_F_ACK);
 
@@ -243,7 +252,7 @@ static int do_cmd(int argc, char **argv, int cmd)
 
 			if (strcmp(*argv, "scope") != 0) {
 				fprintf(stderr, "What is \"%s\"\n", *argv);
-				usage();
+				usage(-1);
 				return -1;
 			}
 			NEXT_ARG();
@@ -270,7 +279,7 @@ static int do_cmd(int argc, char **argv, int cmd)
 				NEXT_ARG();
 				if (strcmp(*argv, "id") != 0) {
 					fprintf(stderr, "What is \"%s\"\n", *argv);
-					usage();
+					usage(-1);
 					return -1;
 				}
 				NEXT_ARG();
@@ -282,7 +291,7 @@ static int do_cmd(int argc, char **argv, int cmd)
 			}
 		} else {
 			fprintf(stderr, "What is \"%s\"\n", *argv);
-			usage();
+			usage(-1);
 			return -1;
 		}
 		argc--;
@@ -363,7 +372,7 @@ static int parse_scope_id(const char *what, int *argcp, char ***argvp, int *scop
 		NEXT_ARG();
 		if (strcmp(*argv, "id") != 0) {
 			fprintf(stderr, "What is \"%s\"\n", *argv);
-			usage();
+			usage(-1);
 			return -1;
 		}
 		NEXT_ARG();
@@ -476,6 +485,13 @@ static int parse_leaves(int *argcp, char ***argvp,
 
 static int do_group(int argc, char **argv)
 {
+	if (argc > 0 && (strcmp(*argv, "help") == 0 ||
+			 strcmp(*argv, "-h") == 0 ||
+			 strcmp(*argv, "--help") == 0)) {
+		usage(0);
+		exit(0);
+	}
+
 	GENL_REQUEST(req, 4096, genl_family, 0, NET_SHAPER_FAMILY_VERSION,
 		     NET_SHAPER_CMD_GROUP, NLM_F_REQUEST | NLM_F_ACK);
 
@@ -517,7 +533,7 @@ static int do_group(int argc, char **argv)
 			continue;
 		} else {
 			fprintf(stderr, "What is \"%s\"\n", *argv);
-			usage();
+			usage(-1);
 			goto free_leaves;
 		}
 		argc--;
@@ -599,6 +615,7 @@ free_leaves:
 int main(int argc, char **argv)
 {
 	int color = default_color_opt();
+	bool need_nl = true;
 
 	while (argc > 1) {
 		const char *opt = argv[1];
@@ -609,7 +626,7 @@ int main(int argc, char **argv)
 			opt++;
 
 		if (strcmp(opt, "-help") == 0) {
-			usage();
+			usage(0);
 			exit(0);
 		} else if (strcmp(opt, "-Version") == 0 ||
 			   strcmp(opt, "-V") == 0) {
@@ -627,8 +644,19 @@ int main(int argc, char **argv)
 
 	check_enable_color(color, 0);
 
-	if (genl_init_handle(&gen_rth, NET_SHAPER_FAMILY_NAME, &genl_family))
-		exit(1);
+	if (argc > 1 && (strcmp(argv[1], "help") == 0 ||
+			 strcmp(argv[1], "-h") == 0 ||
+			 strcmp(argv[1], "--help") == 0))
+		need_nl = false;
+	if (argc > 2 && (strcmp(argv[2], "help") == 0 ||
+			 strcmp(argv[2], "-h") == 0 ||
+			 strcmp(argv[2], "--help") == 0))
+		need_nl = false;
+
+	if (need_nl) {
+		if (genl_init_handle(&gen_rth, NET_SHAPER_FAMILY_NAME, &genl_family))
+			exit(1);
+	}
 
 	if (argc > 1) {
 		argc--;
@@ -643,7 +671,7 @@ int main(int argc, char **argv)
 		if (strcmp(*argv, "group") == 0)
 			return do_group(argc - 1, argv + 1);
 		if (strcmp(*argv, "help") == 0) {
-			usage();
+			usage(0);
 			return 0;
 		}
 		fprintf(stderr,
@@ -651,6 +679,6 @@ int main(int argc, char **argv)
 			*argv);
 		exit(-1);
 	}
-	usage();
+	usage(-1);
 	exit(-1);
 }
diff --git a/rdma/rdma.c b/rdma/rdma.c
index 253ac58b..3bdb76f0 100644
--- a/rdma/rdma.c
+++ b/rdma/rdma.c
@@ -11,9 +11,9 @@
 /* Global utils flags */
 int json;
 
-static void help(char *name)
+static void help(FILE *out, char *name)
 {
-	pr_out("Usage: %s [ OPTIONS ] OBJECT { COMMAND | help }\n"
+	fprintf(out, "Usage: %s [ OPTIONS ] OBJECT { COMMAND | help }\n"
 	       "       %s [ -f[orce] ] -b[atch] filename\n"
 	       "where  OBJECT := { dev | link | resource | monitor | system | statistic | help }\n"
 	       "       OPTIONS := { -V[ersion] | -d[etails] | -j[son] | -p[retty] | -r[aw]}\n", name, name);
@@ -21,14 +21,20 @@ static void help(char *name)
 
 static int cmd_help(struct rd *rd)
 {
-	help(rd->filename);
+	help(stdout, rd->filename);
 	return 0;
 }
 
+static int cmd_no_arg(struct rd *rd)
+{
+	help(stderr, rd->filename);
+	return -EINVAL;
+}
+
 static int rd_cmd(struct rd *rd, int argc, char **argv)
 {
 	const struct rd_cmd cmds[] = {
-		{ NULL,		cmd_help },
+		{ NULL,		cmd_no_arg },
 		{ "help",	cmd_help },
 		{ "dev",	cmd_dev },
 		{ "link",	cmd_link },
@@ -104,6 +110,7 @@ int main(int argc, char **argv)
 	bool show_raw = false;
 	bool force = false;
 	bool oneline = false;
+	bool need_nl = true;
 	struct rd rd = {};
 	char *filename;
 	int opt;
@@ -142,14 +149,14 @@ int main(int argc, char **argv)
 			batch_file = optarg;
 			break;
 		case 'h':
-			help(filename);
+			help(stdout, filename);
 			return EXIT_SUCCESS;
 		case ':':
 			pr_err("-%c option requires an argument\n", optopt);
 			return EXIT_FAILURE;
 		default:
 			pr_err("Unknown option.\n");
-			help(filename);
+			help(stderr, filename);
 			return EXIT_FAILURE;
 		}
 	}
@@ -163,9 +170,20 @@ int main(int argc, char **argv)
 	rd.show_driver_details = show_driver_details;
 	rd.show_raw = show_raw;
 
-	err = rd_init(&rd, filename);
-	if (err)
-		goto out;
+	if (argc > 0 && (strcmp(argv[0], "help") == 0 ||
+			 strcmp(argv[0], "-h") == 0 ||
+			 strcmp(argv[0], "--help") == 0))
+		need_nl = false;
+	if (argc > 1 && (strcmp(argv[1], "help") == 0 ||
+			 strcmp(argv[1], "-h") == 0 ||
+			 strcmp(argv[1], "--help") == 0))
+		need_nl = false;
+
+	if (need_nl) {
+		err = rd_init(&rd, filename);
+		if (err)
+			goto out;
+	}
 
 	if (batch_file)
 		err = rd_batch(&rd, batch_file, force);
@@ -173,6 +191,7 @@ int main(int argc, char **argv)
 		err = rd_cmd(&rd, argc, argv);
 out:
 	/* Always cleanup */
-	rd_cleanup(&rd);
+	if (need_nl)
+		rd_cleanup(&rd);
 	return err ? EXIT_FAILURE : EXIT_SUCCESS;
 }
diff --git a/tc/tc.c b/tc/tc.c
index 7d69e4d5..057541d2 100644
--- a/tc/tc.c
+++ b/tc/tc.c
@@ -186,9 +186,11 @@ noexist:
 	return q;
 }
 
-static void usage(void)
+static void usage(int status)
 {
-	fprintf(stderr,
+	FILE *out = status == 0 ? stdout : stderr;
+	
+	fprintf(out,
 		"Usage:	tc [ OPTIONS ] OBJECT { COMMAND | help }\n"
 		"	tc [-force] -batch filename\n"
 		"where  OBJECT := { qdisc | class | filter | chain |\n"
@@ -217,7 +219,7 @@ static int do_cmd(int argc, char **argv)
 	if (matches(*argv, "exec") == 0)
 		return do_exec(argc-1, argv+1);
 	if (matches(*argv, "help") == 0) {
-		usage();
+		usage(0);
 		return 0;
 	}
 
@@ -282,7 +284,7 @@ int main(int argc, char **argv)
 		} else if (matches(argv[1], "-iec") == 0) {
 			++use_iec;
 		} else if (matches(argv[1], "-help") == 0) {
-			usage();
+			usage(0);
 			return 0;
 		} else if (matches(argv[1], "-force") == 0) {
 			++force;
@@ -335,8 +337,8 @@ int main(int argc, char **argv)
 		return batch(batch_file);
 
 	if (argc <= 1) {
-		usage();
-		return 0;
+		usage(-1);
+		return -1;
 	}
 
 	tc_core_init();
diff --git a/testsuite/tests/ip/help.t b/testsuite/tests/ip/help.t
new file mode 100755
index 00000000..141abdd6
--- /dev/null
+++ b/testsuite/tests/ip/help.t
@@ -0,0 +1,151 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0
+
+. lib/generic.sh
+
+ts_log "[Testing help exit codes and output streams]"
+
+if [ -z "$SNAME" ]; then
+	SNAME="iproute2/iproute2-this"
+fi
+
+# Define binary paths using env vars if available, else fallback to SNAME subpaths
+IP="${IP:-$SNAME/ip/ip}"
+BRIDGE="${BRIDGE:-$SNAME/bridge/bridge}"
+TC="${TC:-$SNAME/tc/tc}"
+DEVLINK="${DEVLINK:-$SNAME/devlink/devlink}"
+DCB="${DCB:-$SNAME/dcb/dcb}"
+RDMA="${RDMA:-$SNAME/rdma/rdma}"
+VDPA="${VDPA:-$SNAME/vdpa/vdpa}"
+DPLL="${DPLL:-$SNAME/dpll/dpll}"
+GENL="${GENL:-$SNAME/genl/genl}"
+TIPC="${TIPC:-$SNAME/tipc/tipc}"
+NETSHAPER="${NETSHAPER:-$SNAME/netshaper/netshaper}"
+
+test_help_cmd() {
+	binary_path="$1"
+	arg="$2"
+	desc="$3"
+
+	$binary_path $arg >$STD_OUT 2>$STD_ERR
+	status=$?
+
+	if [ $status -ne 0 ]; then
+		ts_err "$0: $desc exited with $status (expected 0)"
+		return 1
+	fi
+
+	if [ -s $STD_ERR ]; then
+		ts_err "$0: $desc wrote to stderr (expected stdout only)"
+		ts_err "$0: stderr content:"
+		ts_err_cat $STD_ERR
+		return 1
+	fi
+
+	if [ ! -s $STD_OUT ]; then
+		ts_err "$0: $desc stdout was empty"
+		return 1
+	fi
+
+	echo "$0: $desc passed"
+	return 0
+}
+
+test_usage_error() {
+	binary_path="$1"
+	desc="$2"
+
+	$binary_path >$STD_OUT 2>$STD_ERR
+	status=$?
+
+	if [ $status -eq 0 ]; then
+		ts_err "$0: $desc (no args) exited with 0 (expected non-zero)"
+		return 1
+	fi
+
+	if [ ! -s $STD_ERR ]; then
+		ts_err "$0: $desc (no args) stderr was empty (expected usage info)"
+		return 1
+	fi
+
+	echo "$0: $desc (no args) failed as expected"
+	return 0
+}
+
+test_help_netlink_bypass() {
+	binary_path="$1"
+	arg="$2"
+	desc="$3"
+
+	$binary_path $arg >$STD_OUT 2>$STD_ERR
+	status=$?
+
+	if [ $status -ne 0 ]; then
+		ts_err "$0: $desc exited with $status (expected 0)"
+		return 1
+	fi
+
+	echo "$0: $desc passed"
+	return 0
+}
+
+# Run tests for each utility
+# ip
+test_help_cmd "$IP" "help" "ip help"
+test_help_cmd "$IP" "-help" "ip -help"
+test_usage_error "$IP" "ip"
+
+# bridge
+test_help_cmd "$BRIDGE" "help" "bridge help"
+test_help_cmd "$BRIDGE" "-h" "bridge -h"
+test_usage_error "$BRIDGE" "bridge"
+
+# tc
+test_help_cmd "$TC" "help" "tc help"
+test_help_cmd "$TC" "-h" "tc -h"
+test_usage_error "$TC" "tc"
+
+# devlink
+test_help_cmd "$DEVLINK" "help" "devlink help"
+test_help_cmd "$DEVLINK" "-h" "devlink -h"
+test_help_netlink_bypass "$DEVLINK" "dev help" "devlink dev help"
+test_usage_error "$DEVLINK" "devlink"
+
+# dcb
+test_help_cmd "$DCB" "help" "dcb help"
+test_help_cmd "$DCB" "-h" "dcb -h"
+test_help_netlink_bypass "$DCB" "app help" "dcb app help"
+test_usage_error "$DCB" "dcb"
+
+# rdma
+test_help_cmd "$RDMA" "help" "rdma help"
+test_help_cmd "$RDMA" "-h" "rdma -h"
+test_help_netlink_bypass "$RDMA" "dev help" "rdma dev help"
+test_usage_error "$RDMA" "rdma"
+
+# vdpa
+test_help_cmd "$VDPA" "help" "vdpa help"
+test_help_cmd "$VDPA" "-h" "vdpa -h"
+test_help_netlink_bypass "$VDPA" "dev help" "vdpa dev help"
+test_usage_error "$VDPA" "vdpa"
+
+# dpll
+test_help_cmd "$DPLL" "help" "dpll help"
+test_help_cmd "$DPLL" "-h" "dpll -h"
+test_help_netlink_bypass "$DPLL" "device help" "dpll device help"
+test_usage_error "$DPLL" "dpll"
+
+# genl
+test_help_cmd "$GENL" "-help" "genl -help"
+test_usage_error "$GENL" "genl"
+
+# tipc
+test_help_cmd "$TIPC" "-h" "tipc -h"
+test_help_cmd "$TIPC" "--help" "tipc --help"
+test_usage_error "$TIPC" "tipc"
+
+# netshaper
+test_help_cmd "$NETSHAPER" "help" "netshaper help"
+test_help_cmd "$NETSHAPER" "-help" "netshaper -help"
+test_help_netlink_bypass "$NETSHAPER" "group help" "netshaper group help"
+test_usage_error "$NETSHAPER" "netshaper"
diff --git a/tipc/bearer.c b/tipc/bearer.c
index bb434f5f..2804631b 100644
--- a/tipc/bearer.c
+++ b/tipc/bearer.c
@@ -33,9 +33,9 @@ struct cb_data {
 	struct nlmsghdr *nlh;
 };
 
-static void _print_bearer_opts(void)
+static void _print_bearer_opts(FILE *out)
 {
-	fprintf(stderr,
+	fprintf(out,
 		"OPTIONS\n"
 		" priority		- Bearer link priority\n"
 		" tolerance		- Bearer link tolerance\n"
@@ -43,18 +43,18 @@ static void _print_bearer_opts(void)
 		" mtu			- Bearer link mtu\n");
 }
 
-void print_bearer_media(void)
+void print_bearer_media(FILE *out)
 {
-	fprintf(stderr,
+	fprintf(out,
 		"\nMEDIA\n"
 		" udp			- User Datagram Protocol\n"
 		" ib			- Infiniband\n"
 		" eth			- Ethernet\n");
 }
 
-static void cmd_bearer_enable_l2_help(struct cmdl *cmdl, char *media)
+static void cmd_bearer_enable_l2_help(FILE *out, struct cmdl *cmdl, char *media)
 {
-	fprintf(stderr,
+	fprintf(out,
 		"Usage: %s bearer enable media %s device DEVICE [OPTIONS]\n"
 		"\nOPTIONS\n"
 		" domain DOMAIN		- Discovery domain\n"
@@ -62,9 +62,9 @@ static void cmd_bearer_enable_l2_help(struct cmdl *cmdl, char *media)
 		cmdl->argv[0], media);
 }
 
-static void cmd_bearer_enable_udp_help(struct cmdl *cmdl, char *media)
+static void cmd_bearer_enable_udp_help(FILE *out, struct cmdl *cmdl, char *media)
 {
-	fprintf(stderr,
+	fprintf(out,
 		"Usage: %s bearer enable [OPTIONS] media %s name NAME [localip IP|device DEVICE] [UDP OPTIONS]\n\n"
 		"OPTIONS\n"
 		" domain DOMAIN		- Discovery domain\n"
@@ -230,7 +230,7 @@ static int nl_add_udp_enable_opts(struct nlmsghdr *nlh, struct opt *opts,
 		opt = get_opt(opts, "localip");
 		if (!opt) {
 			fprintf(stderr, "error, udp bearer localip/device missing\n");
-			cmd_bearer_enable_udp_help(cmdl, "udp");
+			cmd_bearer_enable_udp_help(help_flag ? stdout : stderr, cmdl, "udp");
 			return -EINVAL;
 		}
 		locip = opt->val;
@@ -292,7 +292,7 @@ static char *cmd_get_media_type(const struct cmd *cmd, struct cmdl *cmdl,
 
 	if (!opt) {
 		if (help_flag)
-			(cmd->help)(cmdl);
+			(cmd->help)(help_flag ? stdout : stderr, cmdl);
 		else
 			fprintf(stderr, "error, missing bearer media\n");
 		return NULL;
@@ -307,14 +307,14 @@ static int nl_add_bearer_name(struct nlmsghdr *nlh, const struct cmd *cmd,
 	char bname[TIPC_MAX_BEARER_NAME];
 	int err;
 
-	if ((err = cmd_get_unique_bearer_name(cmd, cmdl, opts, bname, sup_media)))
+	if ((err = cmd_get_unique_bearer_name(help_flag ? stdout : stderr, cmd, cmdl, opts, bname, sup_media)))
 		return err;
 
 	mnl_attr_put_strz(nlh, TIPC_NLA_BEARER_NAME, bname);
 	return 0;
 }
 
-int cmd_get_unique_bearer_name(const struct cmd *cmd, struct cmdl *cmdl,
+int cmd_get_unique_bearer_name(FILE *out, const struct cmd *cmd, struct cmdl *cmdl,
 			       struct opt *opts, char *bname,
 			       const struct tipc_sup_media *sup_media)
 {
@@ -332,7 +332,7 @@ int cmd_get_unique_bearer_name(const struct cmd *cmd, struct cmdl *cmdl,
 
 		if (!(opt = get_opt(opts, entry->identifier))) {
 			if (help_flag)
-				(entry->help)(cmdl, media);
+				(entry->help)(out, cmdl, media);
 			else
 				fprintf(stderr, "error, missing bearer %s\n",
 					entry->identifier);
@@ -350,15 +350,15 @@ int cmd_get_unique_bearer_name(const struct cmd *cmd, struct cmdl *cmdl,
 	return -EINVAL;
 }
 
-static void cmd_bearer_add_udp_help(struct cmdl *cmdl, char *media)
+static void cmd_bearer_add_udp_help(FILE *out, struct cmdl *cmdl, char *media)
 {
-	fprintf(stderr, "Usage: %s bearer add media %s name NAME remoteip REMOTEIP\n\n",
+	fprintf(out, "Usage: %s bearer add media %s name NAME remoteip REMOTEIP\n\n",
 		cmdl->argv[0], media);
 }
 
-static void cmd_bearer_add_help(struct cmdl *cmdl)
+static void cmd_bearer_add_help(FILE *out, struct cmdl *cmdl)
 {
-	fprintf(stderr, "Usage: %s bearer add media udp name NAME remoteip REMOTEIP\n",
+	fprintf(out, "Usage: %s bearer add media udp name NAME remoteip REMOTEIP\n",
 		cmdl->argv[0]);
 }
 
@@ -421,6 +421,11 @@ static int cmd_bearer_add_media(struct nlmsghdr *nlh, const struct cmd *cmd,
 		{ NULL, },
 	};
 
+	if (help_flag) {
+		(cmd->help)(stdout, cmdl);
+		return 0;
+	}
+
 	/* Rewind optind to include media in the option list */
 	cmdl->optind--;
 	if (parse_opts(opts, cmdl) < 0)
@@ -473,15 +478,15 @@ static int cmd_bearer_add(struct nlmsghdr *nlh, const struct cmd *cmd,
 	return run_cmd(nlh, cmd, cmds, cmdl, NULL);
 }
 
-static void cmd_bearer_enable_help(struct cmdl *cmdl)
+static void cmd_bearer_enable_help(FILE *out, struct cmdl *cmdl)
 {
-	fprintf(stderr,
+	fprintf(out,
 		"Usage: %s bearer enable [OPTIONS] media MEDIA ARGS...\n\n"
 		"OPTIONS\n"
 		" domain DOMAIN         - Discovery domain\n"
 		" priority PRIORITY     - Bearer priority\n",
 		cmdl->argv[0]);
-	print_bearer_media();
+	print_bearer_media(out);
 }
 
 static int cmd_bearer_enable(struct nlmsghdr *nlh, const struct cmd *cmd,
@@ -509,12 +514,14 @@ static int cmd_bearer_enable(struct nlmsghdr *nlh, const struct cmd *cmd,
 		{ NULL, },
 	};
 
-	if (parse_opts(opts, cmdl) < 0) {
-		if (help_flag)
-			(cmd->help)(cmdl);
-		return -EINVAL;
+	if (help_flag) {
+		(cmd->help)(stdout, cmdl);
+		return 0;
 	}
 
+	if (parse_opts(opts, cmdl) < 0)
+		return -EINVAL;
+
 	nlh = msg_init(TIPC_NL_BEARER_ENABLE);
 	if (!nlh) {
 		fprintf(stderr, "error: message initialisation failed\n");
@@ -548,23 +555,23 @@ static int cmd_bearer_enable(struct nlmsghdr *nlh, const struct cmd *cmd,
 	return msg_doit(nlh, NULL, NULL);
 }
 
-static void cmd_bearer_disable_l2_help(struct cmdl *cmdl, char *media)
+static void cmd_bearer_disable_l2_help(FILE *out, struct cmdl *cmdl, char *media)
 {
-	fprintf(stderr, "Usage: %s bearer disable media %s device DEVICE\n",
+	fprintf(out, "Usage: %s bearer disable media %s device DEVICE\n",
 		cmdl->argv[0], media);
 }
 
-static void cmd_bearer_disable_udp_help(struct cmdl *cmdl, char *media)
+static void cmd_bearer_disable_udp_help(FILE *out, struct cmdl *cmdl, char *media)
 {
-	fprintf(stderr, "Usage: %s bearer disable media %s name NAME\n",
+	fprintf(out, "Usage: %s bearer disable media %s name NAME\n",
 		cmdl->argv[0], media);
 }
 
-static void cmd_bearer_disable_help(struct cmdl *cmdl)
+static void cmd_bearer_disable_help(FILE *out, struct cmdl *cmdl)
 {
-	fprintf(stderr, "Usage: %s bearer disable media MEDIA ARGS...\n",
+	fprintf(out, "Usage: %s bearer disable media MEDIA ARGS...\n",
 		cmdl->argv[0]);
-	print_bearer_media();
+	print_bearer_media(out);
 }
 
 static int cmd_bearer_disable(struct nlmsghdr *nlh, const struct cmd *cmd,
@@ -585,12 +592,14 @@ static int cmd_bearer_disable(struct nlmsghdr *nlh, const struct cmd *cmd,
 		{ NULL, },
 	};
 
-	if (parse_opts(opts, cmdl) < 0) {
-		if (help_flag)
-			(cmd->help)(cmdl);
-		return -EINVAL;
+	if (help_flag) {
+		(cmd->help)(stdout, cmdl);
+		return 0;
 	}
 
+	if (parse_opts(opts, cmdl) < 0)
+		return -EINVAL;
+
 	nlh = msg_init(TIPC_NL_BEARER_DISABLE);
 	if (!nlh) {
 		fprintf(stderr, "error, message initialisation failed\n");
@@ -607,27 +616,27 @@ static int cmd_bearer_disable(struct nlmsghdr *nlh, const struct cmd *cmd,
 
 }
 
-static void cmd_bearer_set_help(struct cmdl *cmdl)
+static void cmd_bearer_set_help(FILE *out, struct cmdl *cmdl)
 {
-	fprintf(stderr, "Usage: %s bearer set OPTION media MEDIA ARGS...\n",
+	fprintf(out, "Usage: %s bearer set OPTION media MEDIA ARGS...\n",
 		cmdl->argv[0]);
-	_print_bearer_opts();
-	print_bearer_media();
+	_print_bearer_opts(out);
+	print_bearer_media(out);
 }
 
-static void cmd_bearer_set_udp_help(struct cmdl *cmdl, char *media)
+static void cmd_bearer_set_udp_help(FILE *out, struct cmdl *cmdl, char *media)
 {
-	fprintf(stderr, "Usage: %s bearer set OPTION media %s name NAME\n\n",
+	fprintf(out, "Usage: %s bearer set OPTION media %s name NAME\n\n",
 		cmdl->argv[0], media);
-	_print_bearer_opts();
+	_print_bearer_opts(out);
 }
 
-static void cmd_bearer_set_l2_help(struct cmdl *cmdl, char *media)
+static void cmd_bearer_set_l2_help(FILE *out, struct cmdl *cmdl, char *media)
 {
-	fprintf(stderr,
+	fprintf(out,
 		"Usage: %s bearer set [OPTION]... media %s device DEVICE\n",
 		cmdl->argv[0], media);
-	_print_bearer_opts();
+	_print_bearer_opts(out);
 }
 
 static int cmd_bearer_set_prop(struct nlmsghdr *nlh, const struct cmd *cmd,
@@ -662,6 +671,11 @@ static int cmd_bearer_set_prop(struct nlmsghdr *nlh, const struct cmd *cmd,
 	else
 		return -EINVAL;
 
+	if (help_flag) {
+		(cmd->help)(stdout, cmdl);
+		return 0;
+	}
+
 	if (cmdl->optind >= cmdl->argc) {
 		fprintf(stderr, "error, missing value\n");
 		return -EINVAL;
@@ -716,33 +730,33 @@ static int cmd_bearer_set(struct nlmsghdr *nlh, const struct cmd *cmd,
 	return run_cmd(nlh, cmd, cmds, cmdl, NULL);
 }
 
-static void cmd_bearer_get_help(struct cmdl *cmdl)
+static void cmd_bearer_get_help(FILE *out, struct cmdl *cmdl)
 {
-	fprintf(stderr, "Usage: %s bearer get [OPTION] media MEDIA ARGS...\n",
+	fprintf(out, "Usage: %s bearer get [OPTION] media MEDIA ARGS...\n",
 		cmdl->argv[0]);
-	_print_bearer_opts();
-	print_bearer_media();
+	_print_bearer_opts(out);
+	print_bearer_media(out);
 }
 
-static void cmd_bearer_get_udp_help(struct cmdl *cmdl, char *media)
+static void cmd_bearer_get_udp_help(FILE *out, struct cmdl *cmdl, char *media)
 {
-	fprintf(stderr, "Usage: %s bearer get [OPTION] media %s name NAME [UDP OPTIONS]\n\n",
+	fprintf(out, "Usage: %s bearer get [OPTION] media %s name NAME [UDP OPTIONS]\n\n",
 		cmdl->argv[0], media);
-	fprintf(stderr,
+	fprintf(out,
 		"UDP OPTIONS\n"
 		" remoteip              - Remote ip address\n"
 		" remoteport            - Remote port\n"
 		" localip               - Local ip address\n"
 		" localport             - Local port\n\n");
-	_print_bearer_opts();
+	_print_bearer_opts(out);
 }
 
-static void cmd_bearer_get_l2_help(struct cmdl *cmdl, char *media)
+static void cmd_bearer_get_l2_help(FILE *out, struct cmdl *cmdl, char *media)
 {
-	fprintf(stderr,
+	fprintf(out,
 		"Usage: %s bearer get OPTION media %s device DEVICE\n",
 		cmdl->argv[0], media);
-	_print_bearer_opts();
+	_print_bearer_opts(out);
 }
 
 
@@ -938,8 +952,8 @@ static int cmd_bearer_get_media(struct nlmsghdr *nlh, const struct cmd *cmd,
 	media = opt->val;
 
 	if (help_flag) {
-		cmd_bearer_get_udp_help(cmdl, media);
-		return -EINVAL;
+		cmd_bearer_get_udp_help(stdout, cmdl, media);
+		return 0;
 	}
 	if (strcmp(media, "udp") != 0) {
 		fprintf(stderr, "error, no \"%s\" media specific options\n", media);
@@ -1004,8 +1018,8 @@ static int cmd_bearer_get_prop(struct nlmsghdr *nlh, const struct cmd *cmd,
 	};
 
 	if (help_flag) {
-		(cmd->help)(cmdl);
-		return -EINVAL;
+		(cmd->help)(stdout, cmdl);
+		return 0;
 	}
 
 	if (strcmp(cmd->cmd, "priority") == 0)
@@ -1090,8 +1104,8 @@ static int cmd_bearer_list(struct nlmsghdr *nlh, const struct cmd *cmd,
 			   struct cmdl *cmdl, void *data)
 {
 	if (help_flag) {
-		fprintf(stderr, "Usage: %s bearer list\n", cmdl->argv[0]);
-		return -EINVAL;
+		fprintf(stdout, "Usage: %s bearer list\n", cmdl->argv[0]);
+		return 0;
 	}
 
 	nlh = msg_init(TIPC_NL_BEARER_GET);
@@ -1103,9 +1117,9 @@ static int cmd_bearer_list(struct nlmsghdr *nlh, const struct cmd *cmd,
 	return msg_dumpit(nlh, bearer_list_cb, NULL);
 }
 
-void cmd_bearer_help(struct cmdl *cmdl)
+void cmd_bearer_help(FILE *out, struct cmdl *cmdl)
 {
-	fprintf(stderr,
+	fprintf(out,
 		"Usage: %s bearer COMMAND [ARGS] ...\n"
 		"\n"
 		"COMMANDS\n"
diff --git a/tipc/bearer.h b/tipc/bearer.h
index a9344659..2389f811 100644
--- a/tipc/bearer.h
+++ b/tipc/bearer.h
@@ -12,11 +12,13 @@
 
 extern int help_flag;
 
+#include <stdio.h>
+
 int cmd_bearer(struct nlmsghdr *nlh, const struct cmd *cmd, struct cmdl *cmdl, void *data);
-void cmd_bearer_help(struct cmdl *cmdl);
+void cmd_bearer_help(FILE *out, struct cmdl *cmdl);
 
-void print_bearer_media(void);
-int cmd_get_unique_bearer_name(const struct cmd *cmd, struct cmdl *cmdl,
+void print_bearer_media(FILE *out);
+int cmd_get_unique_bearer_name(FILE *out, const struct cmd *cmd, struct cmdl *cmdl,
 			       struct opt *opts, char *bname,
 			       const struct tipc_sup_media *sup_media);
 #endif
diff --git a/tipc/cmdl.c b/tipc/cmdl.c
index 152ddb51..ee9e822c 100644
--- a/tipc/cmdl.c
+++ b/tipc/cmdl.c
@@ -108,7 +108,7 @@ int run_cmd(struct nlmsghdr *nlh, const struct cmd *caller,
 
 	if ((cmdl->optind) >= cmdl->argc) {
 		if (caller->help)
-			(caller->help)(cmdl);
+			(caller->help)(help_flag ? stdout : stderr, cmdl);
 		return -EINVAL;
 	}
 	name = cmdl->argv[cmdl->optind];
@@ -118,7 +118,7 @@ int run_cmd(struct nlmsghdr *nlh, const struct cmd *caller,
 	if (!cmd) {
 		/* Show help about last command if we don't find this one */
 		if (help_flag && caller->help) {
-			(caller->help)(cmdl);
+			(caller->help)(stdout, cmdl);
 		} else {
 			fprintf(stderr, "error, invalid command \"%s\"\n", name);
 			fprintf(stderr, "use --help for command help\n");
@@ -126,5 +126,10 @@ int run_cmd(struct nlmsghdr *nlh, const struct cmd *caller,
 		return -EINVAL;
 	}
 
+	if (help_flag && cmdl->optind >= cmdl->argc && cmd->help) {
+		(cmd->help)(stdout, cmdl);
+		return 0;
+	}
+
 	return (cmd->func)(nlh, cmd, cmdl, data);
 }
diff --git a/tipc/cmdl.h b/tipc/cmdl.h
index 18fe51bf..fd8f02d1 100644
--- a/tipc/cmdl.h
+++ b/tipc/cmdl.h
@@ -12,6 +12,8 @@
 
 extern int help_flag;
 
+#include <stdio.h>
+
 enum {
 	OPT_KEY			= (1 << 0),
 	OPT_KEYVAL		= (1 << 1),
@@ -26,14 +28,14 @@ struct cmdl {
 struct tipc_sup_media {
 	char *media;
 	char *identifier;
-	void (*help)(struct cmdl *cmdl, char *media);
+	void (*help)(FILE *out, struct cmdl *cmdl, char *media);
 };
 
 struct cmd {
 	const char *cmd;
 	int (*func)(struct nlmsghdr *nlh, const struct cmd *cmd,
 		    struct cmdl *cmdl, void *data);
-	void (*help)(struct cmdl *cmdl);
+	void (*help)(FILE *out, struct cmdl *cmdl);
 };
 
 struct opt {
diff --git a/tipc/link.c b/tipc/link.c
index f91c3000..48b759d3 100644
--- a/tipc/link.c
+++ b/tipc/link.c
@@ -139,8 +139,8 @@ static int cmd_link_get_prop(struct nlmsghdr *nlh, const struct cmd *cmd,
 		return -EINVAL;
 
 	if (help_flag) {
-		(cmd->help)(cmdl);
-		return -EINVAL;
+		(cmd->help)(stdout, cmdl);
+		return 0;
 	}
 
 	if (parse_opts(opts, cmdl) < 0)
@@ -164,9 +164,9 @@ static int cmd_link_get_prop(struct nlmsghdr *nlh, const struct cmd *cmd,
 	return msg_doit(nlh, link_get_cb, &prop);
 }
 
-static void cmd_link_get_help(struct cmdl *cmdl)
+static void cmd_link_get_help(FILE *out, struct cmdl *cmdl)
 {
-	fprintf(stderr, "Usage: %s link get PPROPERTY link LINK\n\n"
+	fprintf(out, "Usage: %s link get PPROPERTY link LINK\n\n"
 		"PROPERTIES\n"
 		" tolerance             - Get link tolerance\n"
 		" priority              - Get link priority\n"
@@ -224,9 +224,9 @@ static int cmd_link_get_bcast_cb(const struct nlmsghdr *nlh, void *data)
 	return MNL_CB_OK;
 }
 
-static void cmd_link_get_bcast_help(struct cmdl *cmdl)
+static void cmd_link_get_bcast_help(FILE *out, struct cmdl *cmdl)
 {
-	fprintf(stderr, "Usage: %s link get PPROPERTY\n\n"
+	fprintf(out, "Usage: %s link get PPROPERTY\n\n"
 		"PROPERTIES\n"
 		" broadcast             - Get link broadcast\n",
 		cmdl->argv[0]);
@@ -239,8 +239,8 @@ static int cmd_link_get_bcast(struct nlmsghdr *nlh, const struct cmd *cmd,
 	struct nlattr *attrs;
 
 	if (help_flag) {
-		(cmd->help)(cmdl);
-		return -EINVAL;
+		(cmd->help)(stdout, cmdl);
+		return 0;
 	}
 
 	nlh = msg_init(TIPC_NL_LINK_GET);
@@ -269,9 +269,9 @@ static int cmd_link_get(struct nlmsghdr *nlh, const struct cmd *cmd,
 	return run_cmd(nlh, cmd, cmds, cmdl, NULL);
 }
 
-static void cmd_link_stat_reset_help(struct cmdl *cmdl)
+static void cmd_link_stat_reset_help(FILE *out, struct cmdl *cmdl)
 {
-	fprintf(stderr, "Usage: %s link stat reset link LINK\n\n", cmdl->argv[0]);
+	fprintf(out, "Usage: %s link stat reset link LINK\n\n", cmdl->argv[0]);
 }
 
 static int cmd_link_stat_reset(struct nlmsghdr *nlh, const struct cmd *cmd,
@@ -286,12 +286,12 @@ static int cmd_link_stat_reset(struct nlmsghdr *nlh, const struct cmd *cmd,
 	};
 
 	if (help_flag) {
-		(cmd->help)(cmdl);
-		return -EINVAL;
+		(cmd->help)(stdout, cmdl);
+		return 0;
 	}
 
 	if (parse_opts(opts, cmdl) != 1) {
-		(cmd->help)(cmdl);
+		(cmd->help)(stdout, cmdl);
 		return -EINVAL;
 	}
 
@@ -533,9 +533,9 @@ static int link_stat_show_cb(const struct nlmsghdr *nlh, void *data)
 	return _show_link_stat(name, attrs, prop, stats);
 }
 
-static void cmd_link_stat_show_help(struct cmdl *cmdl)
+static void cmd_link_stat_show_help(FILE *out, struct cmdl *cmdl)
 {
-	fprintf(stderr, "Usage: %s link stat show [ link { LINK | SUBSTRING | all } ]\n",
+	fprintf(out, "Usage: %s link stat show [ link { LINK | SUBSTRING | all } ]\n",
 		cmdl->argv[0]);
 }
 
@@ -552,8 +552,8 @@ static int cmd_link_stat_show(struct nlmsghdr *nlh, const struct cmd *cmd,
 	int err = 0;
 
 	if (help_flag) {
-		(cmd->help)(cmdl);
-		return -EINVAL;
+		(cmd->help)(stdout, cmdl);
+		return 0;
 	}
 
 	nlh = msg_init(TIPC_NL_LINK_GET);
@@ -581,9 +581,9 @@ static int cmd_link_stat_show(struct nlmsghdr *nlh, const struct cmd *cmd,
 	return err;
 }
 
-static void cmd_link_stat_help(struct cmdl *cmdl)
+static void cmd_link_stat_help(FILE *out, struct cmdl *cmdl)
 {
-	fprintf(stderr, "Usage: %s link stat COMMAND [ARGS]\n\n"
+	fprintf(out, "Usage: %s link stat COMMAND [ARGS]\n\n"
 		"COMMANDS:\n"
 		" reset                 - Reset link statistics for link\n"
 		" show                  - Get link priority\n",
@@ -602,9 +602,9 @@ static int cmd_link_stat(struct nlmsghdr *nlh, const struct cmd *cmd,
 	return run_cmd(nlh, cmd, cmds, cmdl, NULL);
 }
 
-static void cmd_link_set_help(struct cmdl *cmdl)
+static void cmd_link_set_help(FILE *out, struct cmdl *cmdl)
 {
-	fprintf(stderr, "Usage: %s link set PPROPERTY link LINK\n\n"
+	fprintf(out, "Usage: %s link set PROPERTY link LINK\n\n"
 		"PROPERTIES\n"
 		" tolerance TOLERANCE   - Set link tolerance\n"
 		" priority PRIORITY     - Set link priority\n"
@@ -636,8 +636,8 @@ static int cmd_link_set_prop(struct nlmsghdr *nlh, const struct cmd *cmd,
 		return -EINVAL;
 
 	if (help_flag) {
-		(cmd->help)(cmdl);
-		return -EINVAL;
+		(cmd->help)(stdout, cmdl);
+		return 0;
 	}
 
 	if (cmdl->optind >= cmdl->argc) {
@@ -672,9 +672,9 @@ static int cmd_link_set_prop(struct nlmsghdr *nlh, const struct cmd *cmd,
 	return msg_doit(nlh, link_get_cb, &prop);
 }
 
-static void cmd_link_set_bcast_help(struct cmdl *cmdl)
+static void cmd_link_set_bcast_help(FILE *out, struct cmdl *cmdl)
 {
-	fprintf(stderr, "Usage: %s link set broadcast PROPERTY\n\n"
+	fprintf(out, "Usage: %s link set broadcast PROPERTY\n\n"
 		"PROPERTIES\n"
 		" BROADCAST         - Forces all multicast traffic to be\n"
 		"                     transmitted via broadcast only,\n"
@@ -708,8 +708,8 @@ static int cmd_link_set_bcast(struct nlmsghdr *nlh, const struct cmd *cmd,
 	int method = 0;
 
 	if (help_flag) {
-		(cmd->help)(cmdl);
-		return -EINVAL;
+		(cmd->help)(stdout, cmdl);
+		return 0;
 	}
 
 	if (parse_opts(opts, cmdl) < 0)
@@ -720,7 +720,7 @@ static int cmd_link_set_bcast(struct nlmsghdr *nlh, const struct cmd *cmd,
 			break;
 
 	if (!opt || !opt->key) {
-		(cmd->help)(cmdl);
+		(cmd->help)(stdout, cmdl);
 		return -EINVAL;
 	}
 
@@ -744,7 +744,7 @@ static int cmd_link_set_bcast(struct nlmsghdr *nlh, const struct cmd *cmd,
 
 	opt = get_opt(opts, "ratio");
 	if (!method && !opt) {
-		(cmd->help)(cmdl);
+		(cmd->help)(stdout, cmdl);
 		return -EINVAL;
 	}
 
@@ -833,8 +833,8 @@ static int cmd_link_mon_summary(struct nlmsghdr *nlh, const struct cmd *cmd,
 	int err = 0;
 
 	if (help_flag) {
-		fprintf(stderr,	"Usage: %s monitor summary\n", cmdl->argv[0]);
-		return -EINVAL;
+		fprintf(stdout,	"Usage: %s monitor summary\n", cmdl->argv[0]);
+		return 0;
 	}
 
 	nlh = msg_init(TIPC_NL_MON_GET);
@@ -1052,23 +1052,23 @@ static int link_mon_list_cb(const struct nlmsghdr *nlh, void *data)
 	return MNL_CB_OK;
 }
 
-static void cmd_link_mon_list_help(struct cmdl *cmdl)
+static void cmd_link_mon_list_help(FILE *out, struct cmdl *cmdl)
 {
-	fprintf(stderr, "Usage: %s monitor list [ media MEDIA ARGS...]\n\n",
+	fprintf(out, "Usage: %s monitor list [ media MEDIA ARGS...]\n\n",
 		cmdl->argv[0]);
-	print_bearer_media();
+	print_bearer_media(out);
 }
 
-static void cmd_link_mon_list_l2_help(struct cmdl *cmdl, char *media)
+static void cmd_link_mon_list_l2_help(FILE *out, struct cmdl *cmdl, char *media)
 {
-	fprintf(stderr,
+	fprintf(out,
 		"Usage: %s monitor list media %s device DEVICE [OPTIONS]\n",
 		cmdl->argv[0], media);
 }
 
-static void cmd_link_mon_list_udp_help(struct cmdl *cmdl, char *media)
+static void cmd_link_mon_list_udp_help(FILE *out, struct cmdl *cmdl, char *media)
 {
-	fprintf(stderr,
+	fprintf(out,
 		"Usage: %s monitor list media udp name NAME\n\n",
 		cmdl->argv[0]);
 }
@@ -1096,15 +1096,15 @@ static int cmd_link_mon_list(struct nlmsghdr *nlh, const struct cmd *cmd,
 		return -EINVAL;
 
 	if (get_opt(opts, "media")) {
-		err = cmd_get_unique_bearer_name(cmd, cmdl, opts, bname,
+		err = cmd_get_unique_bearer_name(help_flag ? stdout : stderr, cmd, cmdl, opts, bname,
 						 sup_media);
 		if (err)
 			return err;
 	}
 
 	if (help_flag) {
-		cmd->help(cmdl);
-		return -EINVAL;
+		cmd->help(stdout, cmdl);
+		return 0;
 	}
 
 	nlh = msg_init(TIPC_NL_MON_GET);
@@ -1119,9 +1119,9 @@ static int cmd_link_mon_list(struct nlmsghdr *nlh, const struct cmd *cmd,
 	return err;
 }
 
-static void cmd_link_mon_set_help(struct cmdl *cmdl)
+static void cmd_link_mon_set_help(FILE *out, struct cmdl *cmdl)
 {
-	fprintf(stderr, "Usage: %s monitor set PPROPERTY\n\n"
+	fprintf(out, "Usage: %s monitor set PROPERTY\n\n"
 		"PROPERTIES\n"
 		" threshold SIZE	- Set monitor activation threshold\n",
 		cmdl->argv[0]);
@@ -1131,16 +1131,16 @@ static int cmd_link_mon_set(struct nlmsghdr *nlh, const struct cmd *cmd,
 			    struct cmdl *cmdl, void *data)
 {
 	const struct cmd cmds[] = {
-		{ "threshold",	cmd_link_mon_set_prop,	NULL },
+		{ "threshold",	cmd_link_mon_set_prop,	cmd_link_mon_set_help },
 		{ NULL }
 	};
 
 	return run_cmd(nlh, cmd, cmds, cmdl, NULL);
 }
 
-static void cmd_link_mon_get_help(struct cmdl *cmdl)
+static void cmd_link_mon_get_help(FILE *out, struct cmdl *cmdl)
 {
-	fprintf(stderr, "Usage: %s monitor get PPROPERTY\n\n"
+	fprintf(out, "Usage: %s monitor get PROPERTY\n\n"
 		"PROPERTIES\n"
 		" threshold	- Get monitor activation threshold\n",
 		cmdl->argv[0]);
@@ -1171,6 +1171,10 @@ static int link_mon_get_cb(const struct nlmsghdr *nlh, void *data)
 static int cmd_link_mon_get_prop(struct nlmsghdr *nlh, const struct cmd *cmd,
 				 struct cmdl *cmdl, void *data)
 {
+	if (help_flag) {
+		(cmd->help)(stdout, cmdl);
+		return 0;
+	}
 
 	nlh = msg_init(TIPC_NL_MON_GET);
 	if (!nlh) {
@@ -1185,17 +1189,17 @@ static int cmd_link_mon_get(struct nlmsghdr *nlh, const struct cmd *cmd,
 			    struct cmdl *cmdl, void *data)
 {
 	const struct cmd cmds[] = {
-		{ "threshold",	cmd_link_mon_get_prop,	NULL},
+		{ "threshold",	cmd_link_mon_get_prop,	cmd_link_mon_get_help},
 		{ NULL }
 	};
 
 	return run_cmd(nlh, cmd, cmds, cmdl, NULL);
 }
 
-static void cmd_link_mon_help(struct cmdl *cmdl)
+static void cmd_link_mon_help(FILE *out, struct cmdl *cmdl)
 {
-	fprintf(stderr,
-		"Usage: %s montior COMMAND [ARGS] ...\n\n"
+	fprintf(out,
+		"Usage: %s monitor COMMAND [ARGS] ...\n\n"
 		"COMMANDS\n"
 		" set			- Set monitor properties\n"
 		" get			- Get monitor properties\n"
@@ -1218,9 +1222,9 @@ static int cmd_link_mon(struct nlmsghdr *nlh, const struct cmd *cmd, struct cmdl
 	return run_cmd(nlh, cmd, cmds, cmdl, NULL);
 }
 
-void cmd_link_help(struct cmdl *cmdl)
+void cmd_link_help(FILE *out, struct cmdl *cmdl)
 {
-	fprintf(stderr,
+	fprintf(out,
 		"Usage: %s link COMMAND [ARGS] ...\n"
 		"\n"
 		"COMMANDS\n"
diff --git a/tipc/link.h b/tipc/link.h
index a0d46035..09e47b89 100644
--- a/tipc/link.h
+++ b/tipc/link.h
@@ -8,10 +8,10 @@
 #ifndef _TIPC_LINK_H
 #define _TIPC_LINK_H
 
-extern int help_flag;
+#include <stdio.h>
 
 int cmd_link(struct nlmsghdr *nlh, const struct cmd *cmd, struct cmdl *cmdl,
 	     void *data);
-void cmd_link_help(struct cmdl *cmdl);
+void cmd_link_help(FILE *out, struct cmdl *cmdl);
 
 #endif
diff --git a/tipc/media.c b/tipc/media.c
index 5ff0c8c4..82ce5145 100644
--- a/tipc/media.c
+++ b/tipc/media.c
@@ -40,8 +40,8 @@ static int cmd_media_list(struct nlmsghdr *nlh, const struct cmd *cmd,
 			 struct cmdl *cmdl, void *data)
 {
 	if (help_flag) {
-		fprintf(stderr, "Usage: %s media list\n", cmdl->argv[0]);
-		return -EINVAL;
+		fprintf(stdout, "Usage: %s media list\n", cmdl->argv[0]);
+		return 0;
 	}
 
 	nlh = msg_init(TIPC_NL_MEDIA_GET);
@@ -101,8 +101,8 @@ static int cmd_media_get_prop(struct nlmsghdr *nlh, const struct cmd *cmd,
 		return -EINVAL;
 
 	if (help_flag) {
-		(cmd->help)(cmdl);
-		return -EINVAL;
+		(cmd->help)(stdout, cmdl);
+		return 0;
 	}
 
 	if (parse_opts(opts, cmdl) < 0)
@@ -131,9 +131,9 @@ static int cmd_media_get_prop(struct nlmsghdr *nlh, const struct cmd *cmd,
 	return msg_doit(nlh, media_get_cb, &prop);
 }
 
-static void cmd_media_get_help(struct cmdl *cmdl)
+static void cmd_media_get_help(FILE *out, struct cmdl *cmdl)
 {
-	fprintf(stderr, "Usage: %s media get PPROPERTY media MEDIA\n\n"
+	fprintf(out, "Usage: %s media get PPROPERTY media MEDIA\n\n"
 		"PROPERTIES\n"
 		" tolerance             - Get media tolerance\n"
 		" priority              - Get media priority\n"
@@ -156,9 +156,9 @@ static int cmd_media_get(struct nlmsghdr *nlh, const struct cmd *cmd,
 	return run_cmd(nlh, cmd, cmds, cmdl, NULL);
 }
 
-static void cmd_media_set_help(struct cmdl *cmdl)
+static void cmd_media_set_help(FILE *out, struct cmdl *cmdl)
 {
-	fprintf(stderr, "Usage: %s media set PPROPERTY media MEDIA\n\n"
+	fprintf(out, "Usage: %s media set PPROPERTY media MEDIA\n\n"
 		"PROPERTIES\n"
 		" tolerance TOLERANCE   - Set media tolerance\n"
 		" priority PRIORITY     - Set media priority\n"
@@ -192,8 +192,8 @@ static int cmd_media_set_prop(struct nlmsghdr *nlh, const struct cmd *cmd,
 		return -EINVAL;
 
 	if (help_flag) {
-		(cmd->help)(cmdl);
-		return -EINVAL;
+		(cmd->help)(stdout, cmdl);
+		return 0;
 	}
 
 	if (cmdl->optind >= cmdl->argc) {
@@ -247,9 +247,9 @@ static int cmd_media_set(struct nlmsghdr *nlh, const struct cmd *cmd,
 	return run_cmd(nlh, cmd, cmds, cmdl, NULL);
 }
 
-void cmd_media_help(struct cmdl *cmdl)
+void cmd_media_help(FILE *out, struct cmdl *cmdl)
 {
-	fprintf(stderr,
+	fprintf(out,
 		"Usage: %s media COMMAND [ARGS] ...\n"
 		"\n"
 		"Commands:\n"
diff --git a/tipc/media.h b/tipc/media.h
index f1b4b540..eb4374dd 100644
--- a/tipc/media.h
+++ b/tipc/media.h
@@ -8,10 +8,10 @@
 #ifndef _TIPC_MEDIA_H
 #define _TIPC_MEDIA_H
 
-extern int help_flag;
+#include <stdio.h>
 
 int cmd_media(struct nlmsghdr *nlh, const struct cmd *cmd, struct cmdl *cmdl,
 	     void *data);
-void cmd_media_help(struct cmdl *cmdl);
+void cmd_media_help(FILE *out, struct cmdl *cmdl);
 
 #endif
diff --git a/tipc/nametable.c b/tipc/nametable.c
index 5162f7fc..1ede1024 100644
--- a/tipc/nametable.c
+++ b/tipc/nametable.c
@@ -80,8 +80,8 @@ static int cmd_nametable_show(struct nlmsghdr *nlh, const struct cmd *cmd,
 	int rc = 0;
 
 	if (help_flag) {
-		fprintf(stderr, "Usage: %s nametable show\n", cmdl->argv[0]);
-		return -EINVAL;
+		fprintf(stdout, "Usage: %s nametable show\n", cmdl->argv[0]);
+		return 0;
 	}
 
 	nlh = msg_init(TIPC_NL_NAME_TABLE_GET);
@@ -97,9 +97,9 @@ static int cmd_nametable_show(struct nlmsghdr *nlh, const struct cmd *cmd,
 	return rc;
 }
 
-void cmd_nametable_help(struct cmdl *cmdl)
+void cmd_nametable_help(FILE *out, struct cmdl *cmdl)
 {
-	fprintf(stderr,
+	fprintf(out,
 		"Usage: %s nametable COMMAND\n\n"
 		"COMMANDS\n"
 		" show                  - Show nametable\n",
diff --git a/tipc/nametable.h b/tipc/nametable.h
index c4df8d9d..e84227ed 100644
--- a/tipc/nametable.h
+++ b/tipc/nametable.h
@@ -8,9 +8,9 @@
 #ifndef _TIPC_NAMETABLE_H
 #define _TIPC_NAMETABLE_H
 
-extern int help_flag;
+#include <stdio.h>
 
-void cmd_nametable_help(struct cmdl *cmdl);
+void cmd_nametable_help(FILE *out, struct cmdl *cmdl);
 int cmd_nametable(struct nlmsghdr *nlh, const struct cmd *cmd, struct cmdl *cmdl,
 		  void *data);
 
diff --git a/tipc/node.c b/tipc/node.c
index b84a3fa1..77abc185 100644
--- a/tipc/node.c
+++ b/tipc/node.c
@@ -48,8 +48,8 @@ static int cmd_node_list(struct nlmsghdr *nlh, const struct cmd *cmd,
 			 struct cmdl *cmdl, void *data)
 {
 	if (help_flag) {
-		fprintf(stderr, "Usage: %s node list\n", cmdl->argv[0]);
-		return -EINVAL;
+		fprintf(stdout, "Usage: %s node list\n", cmdl->argv[0]);
+		return 0;
 	}
 
 	nlh = msg_init(TIPC_NL_NODE_GET);
@@ -68,6 +68,11 @@ static int cmd_node_set_addr(struct nlmsghdr *nlh, const struct cmd *cmd,
 	uint32_t addr;
 	struct nlattr *nest;
 
+	if (help_flag) {
+		fprintf(stdout, "Usage: %s node set address ADDRESS\n", cmdl->argv[0]);
+		return 0;
+	}
+
 	if (cmdl->argc != cmdl->optind + 1) {
 		fprintf(stderr, "Usage: %s node set address ADDRESS\n",
 			cmdl->argv[0]);
@@ -126,6 +131,11 @@ static int cmd_node_set_nodeid(struct nlmsghdr *nlh, const struct cmd *cmd,
 	struct nlattr *nest;
 	char *str;
 
+	if (help_flag) {
+		fprintf(stdout, "Usage: %s node set nodeid NODE_ID\n", cmdl->argv[0]);
+		return 0;
+	}
+
 	if (cmdl->argc != cmdl->optind + 1) {
 		fprintf(stderr, "Usage: %s node set nodeid NODE_ID\n",
 			cmdl->argv[0]);
@@ -150,9 +160,9 @@ static int cmd_node_set_nodeid(struct nlmsghdr *nlh, const struct cmd *cmd,
 	return msg_doit(nlh, NULL, NULL);
 }
 
-static void cmd_node_set_key_help(struct cmdl *cmdl)
+static void cmd_node_set_key_help(FILE *out, struct cmdl *cmdl)
 {
-	fprintf(stderr,
+	fprintf(out,
 		"Usage: %s node set key KEY [algname ALGNAME] [PROPERTIES]\n"
 		"       %s node set key rekeying REKEYING\n\n"
 		"KEY\n"
@@ -201,8 +211,8 @@ static int cmd_node_set_key(struct nlmsghdr *nlh, const struct cmd *cmd,
 	char *str;
 
 	if (help_flag || cmdl->optind >= cmdl->argc) {
-		(cmd->help)(cmdl);
-		return -EINVAL;
+		(cmd->help)(help_flag ? stdout : stderr, cmdl);
+		return help_flag ? 0 : -EINVAL;
 	}
 
 	/* Check if command starts with opts i.e. "rekeying" opt without key */
@@ -285,8 +295,8 @@ static int cmd_node_flush_key(struct nlmsghdr *nlh, const struct cmd *cmd,
 			      struct cmdl *cmdl, void *data)
 {
 	if (help_flag) {
-		(cmd->help)(cmdl);
-		return -EINVAL;
+		(cmd->help)(stdout, cmdl);
+		return 0;
 	}
 
 	/* Init & do the command */
@@ -328,8 +338,8 @@ static int cmd_node_get_nodeid(struct nlmsghdr *nlh, const struct cmd *cmd,
 			       struct cmdl *cmdl, void *data)
 {
 	if (help_flag) {
-		(cmd->help)(cmdl);
-		return -EINVAL;
+		(cmd->help)(stdout, cmdl);
+		return 0;
 	}
 
 	nlh = msg_init(TIPC_NL_NET_GET);
@@ -364,8 +374,8 @@ static int cmd_node_get_netid(struct nlmsghdr *nlh, const struct cmd *cmd,
 			      struct cmdl *cmdl, void *data)
 {
 	if (help_flag) {
-		(cmd->help)(cmdl);
-		return -EINVAL;
+		(cmd->help)(stdout, cmdl);
+		return 0;
 	}
 
 	nlh = msg_init(TIPC_NL_NET_GET);
@@ -384,8 +394,8 @@ static int cmd_node_set_netid(struct nlmsghdr *nlh, const struct cmd *cmd,
 	struct nlattr *nest;
 
 	if (help_flag) {
-		(cmd->help)(cmdl);
-		return -EINVAL;
+		(cmd->help)(stdout, cmdl);
+		return 0;
 	}
 
 	nlh = msg_init(TIPC_NL_NET_SET);
@@ -408,9 +418,9 @@ static int cmd_node_set_netid(struct nlmsghdr *nlh, const struct cmd *cmd,
 	return msg_doit(nlh, NULL, NULL);
 }
 
-static void cmd_node_flush_help(struct cmdl *cmdl)
+static void cmd_node_flush_help(FILE *out, struct cmdl *cmdl)
 {
-	fprintf(stderr,
+	fprintf(out,
 		"Usage: %s node flush PROPERTY\n\n"
 		"PROPERTIES\n"
 		" key                   - Flush all symmetric-keys\n",
@@ -428,9 +438,9 @@ static int cmd_node_flush(struct nlmsghdr *nlh, const struct cmd *cmd,
 	return run_cmd(nlh, cmd, cmds, cmdl, NULL);
 }
 
-static void cmd_node_set_help(struct cmdl *cmdl)
+static void cmd_node_set_help(FILE *out, struct cmdl *cmdl)
 {
-	fprintf(stderr,
+	fprintf(out,
 		"Usage: %s node set PROPERTY\n\n"
 		"PROPERTIES\n"
 		" identity NODEID       - Set node identity\n"
@@ -454,9 +464,9 @@ static int cmd_node_set(struct nlmsghdr *nlh, const struct cmd *cmd,
 	return run_cmd(nlh, cmd, cmds, cmdl, NULL);
 }
 
-static void cmd_node_get_help(struct cmdl *cmdl)
+static void cmd_node_get_help(FILE *out, struct cmdl *cmdl)
 {
-	fprintf(stderr,
+	fprintf(out,
 		"Usage: %s node get PROPERTY\n\n"
 		"PROPERTIES\n"
 		" identity              - Get node identity\n"
@@ -478,9 +488,9 @@ static int cmd_node_get(struct nlmsghdr *nlh, const struct cmd *cmd,
 	return run_cmd(nlh, cmd, cmds, cmdl, NULL);
 }
 
-void cmd_node_help(struct cmdl *cmdl)
+void cmd_node_help(FILE *out, struct cmdl *cmdl)
 {
-	fprintf(stderr,
+	fprintf(out,
 		"Usage: %s node COMMAND [ARGS] ...\n\n"
 		"COMMANDS\n"
 		" list                  - List remote nodes\n"
diff --git a/tipc/node.h b/tipc/node.h
index 4a986d07..7d310897 100644
--- a/tipc/node.h
+++ b/tipc/node.h
@@ -8,10 +8,10 @@
 #ifndef _TIPC_NODE_H
 #define _TIPC_NODE_H
 
-extern int help_flag;
+#include <stdio.h>
 
 int cmd_node(struct nlmsghdr *nlh, const struct cmd *cmd, struct cmdl *cmdl,
 	     void *data);
-void cmd_node_help(struct cmdl *cmdl);
+void cmd_node_help(FILE *out, struct cmdl *cmdl);
 
 #endif
diff --git a/tipc/peer.c b/tipc/peer.c
index 5a583fb9..6d12fb19 100644
--- a/tipc/peer.c
+++ b/tipc/peer.c
@@ -27,9 +27,9 @@ static int cmd_peer_rm_addr(struct nlmsghdr *nlh, const struct cmd *cmd,
 	struct nlattr *nest;
 
 	if ((cmdl->argc != cmdl->optind + 1) || help_flag) {
-		fprintf(stderr, "Usage: %s peer remove address ADDRESS\n",
+		fprintf(help_flag ? stdout : stderr, "Usage: %s peer remove address ADDRESS\n",
 			cmdl->argv[0]);
-		return -EINVAL;
+		return help_flag ? 0 : -EINVAL;
 	}
 
 	str = shift_cmdl(cmdl);
@@ -63,10 +63,10 @@ static int cmd_peer_rm_nodeid(struct nlmsghdr *nlh, const struct cmd *cmd,
 	struct nlattr *nest;
 	char *str;
 
-	if (cmdl->argc != cmdl->optind + 1) {
-		fprintf(stderr, "Usage: %s peer remove identity NODEID\n",
+	if ((cmdl->argc != cmdl->optind + 1) || help_flag) {
+		fprintf(help_flag ? stdout : stderr, "Usage: %s peer remove identity NODEID\n",
 			cmdl->argv[0]);
-		return -EINVAL;
+		return help_flag ? 0 : -EINVAL;
 	}
 
 	str = shift_cmdl(cmdl);
@@ -89,23 +89,23 @@ static int cmd_peer_rm_nodeid(struct nlmsghdr *nlh, const struct cmd *cmd,
 	return msg_doit(nlh, NULL, NULL);
 }
 
-static void cmd_peer_rm_help(struct cmdl *cmdl)
+static void cmd_peer_rm_help(FILE *out, struct cmdl *cmdl)
 {
-	fprintf(stderr, "Usage: %s peer remove PROPERTY\n\n"
+	fprintf(out, "Usage: %s peer remove PROPERTY\n\n"
 		"PROPERTIES\n"
 		" identity NODEID         - Remove peer node identity\n",
 		cmdl->argv[0]);
 }
 
-static void cmd_peer_rm_addr_help(struct cmdl *cmdl)
+static void cmd_peer_rm_addr_help(FILE *out, struct cmdl *cmdl)
 {
-	fprintf(stderr, "Usage: %s peer remove address ADDRESS\n",
+	fprintf(out, "Usage: %s peer remove address ADDRESS\n",
 		cmdl->argv[0]);
 }
 
-static void cmd_peer_rm_nodeid_help(struct cmdl *cmdl)
+static void cmd_peer_rm_nodeid_help(FILE *out, struct cmdl *cmdl)
 {
-	fprintf(stderr, "Usage: %s peer remove identity NODEID\n",
+	fprintf(out, "Usage: %s peer remove identity NODEID\n",
 		cmdl->argv[0]);
 }
 
@@ -121,9 +121,9 @@ static int cmd_peer_rm(struct nlmsghdr *nlh, const struct cmd *cmd,
 	return run_cmd(nlh, cmd, cmds, cmdl, NULL);
 }
 
-void cmd_peer_help(struct cmdl *cmdl)
+void cmd_peer_help(FILE *out, struct cmdl *cmdl)
 {
-	fprintf(stderr,
+	fprintf(out,
 		"Usage: %s peer COMMAND [ARGS] ...\n\n"
 		"COMMANDS\n"
 		" remove                - Remove an offline peer node\n",
diff --git a/tipc/peer.h b/tipc/peer.h
index 2bd0a2a3..8a2374cf 100644
--- a/tipc/peer.h
+++ b/tipc/peer.h
@@ -8,10 +8,10 @@
 #ifndef _TIPC_PEER_H
 #define _TIPC_PEER_H
 
-extern int help_flag;
+#include <stdio.h>
 
 int cmd_peer(struct nlmsghdr *nlh, const struct cmd *cmd, struct cmdl *cmdl,
 	     void *data);
-void cmd_peer_help(struct cmdl *cmdl);
+void cmd_peer_help(FILE *out, struct cmdl *cmdl);
 
 #endif
diff --git a/tipc/socket.c b/tipc/socket.c
index 4d376e07..bf2147cb 100644
--- a/tipc/socket.c
+++ b/tipc/socket.c
@@ -112,8 +112,8 @@ static int cmd_socket_list(struct nlmsghdr *nlh, const struct cmd *cmd,
 			   struct cmdl *cmdl, void *data)
 {
 	if (help_flag) {
-		fprintf(stderr, "Usage: %s socket list\n", cmdl->argv[0]);
-		return -EINVAL;
+		fprintf(stdout, "Usage: %s socket list\n", cmdl->argv[0]);
+		return 0;
 	}
 
 	nlh = msg_init(TIPC_NL_SOCK_GET);
@@ -125,9 +125,9 @@ static int cmd_socket_list(struct nlmsghdr *nlh, const struct cmd *cmd,
 	return msg_dumpit(nlh, sock_list_cb, NULL);
 }
 
-void cmd_socket_help(struct cmdl *cmdl)
+void cmd_socket_help(FILE *out, struct cmdl *cmdl)
 {
-	fprintf(stderr,
+	fprintf(out,
 		"Usage: %s socket COMMAND\n\n"
 		"Commands:\n"
 		" list                  - List sockets (ports)\n",
diff --git a/tipc/socket.h b/tipc/socket.h
index c4341bb2..207b8666 100644
--- a/tipc/socket.h
+++ b/tipc/socket.h
@@ -8,9 +8,9 @@
 #ifndef _TIPC_SOCKET_H
 #define _TIPC_SOCKET_H
 
-extern int help_flag;
+#include <stdio.h>
 
-void cmd_socket_help(struct cmdl *cmdl);
+void cmd_socket_help(FILE *out, struct cmdl *cmdl);
 int cmd_socket(struct nlmsghdr *nlh, const struct cmd *cmd, struct cmdl *cmdl,
 		  void *data);
 
diff --git a/tipc/tipc.c b/tipc/tipc.c
index 56af052c..733acb50 100644
--- a/tipc/tipc.c
+++ b/tipc/tipc.c
@@ -28,9 +28,9 @@ int help_flag;
 int json;
 struct mnlu_gen_socket tipc_nlg;
 
-static void about(struct cmdl *cmdl)
+static void about(FILE *out, struct cmdl *cmdl)
 {
-	fprintf(stderr,
+	fprintf(out,
 		"Transparent Inter-Process Communication Protocol\n"
 		"Usage: %s [OPTIONS] COMMAND [ARGS] ...\n"
 		"\n"
@@ -111,20 +111,24 @@ int main(int argc, char *argv[])
 	cmdl.argc = argc;
 	cmdl.argv = argv;
 
-	res = mnlu_gen_socket_open(&tipc_nlg, TIPC_GENL_V2_NAME,
-				   TIPC_GENL_V2_VERSION);
-	if (res) {
-		fprintf(stderr,
-			"Unable to get TIPC nl family id (module loaded?)\n");
-		return -1;
+	if (!help_flag) {
+		res = mnlu_gen_socket_open(&tipc_nlg, TIPC_GENL_V2_NAME,
+					   TIPC_GENL_V2_VERSION);
+		if (res) {
+			fprintf(stderr,
+				"Unable to get TIPC nl family id (module loaded?)\n");
+			return -1;
+		}
 	}
 
 	res = run_cmd(NULL, &cmd, cmds, &cmdl, &tipc_nlg);
 	if (res != 0) {
-		mnlu_gen_socket_close(&tipc_nlg);
-		return -1;
+		if (!help_flag)
+			mnlu_gen_socket_close(&tipc_nlg);
+		return help_flag ? 0 : -1;
 	}
 
-	mnlu_gen_socket_close(&tipc_nlg);
+	if (!help_flag)
+		mnlu_gen_socket_close(&tipc_nlg);
 	return 0;
 }
diff --git a/vdpa/vdpa.c b/vdpa/vdpa.c
index e2b0a5b1..550443e6 100644
--- a/vdpa/vdpa.c
+++ b/vdpa/vdpa.c
@@ -1051,9 +1051,9 @@ static int cmd_dev(struct vdpa *vdpa, int argc, char **argv)
 	return -ENOENT;
 }
 
-static void help(void)
+static void help(FILE *out)
 {
-	fprintf(stderr,
+	fprintf(out,
 		"Usage: vdpa [ OPTIONS ] OBJECT { COMMAND | help }\n"
 		"where  OBJECT := { mgmtdev | dev }\n"
 		"       OPTIONS := { -V[ersion] | -n[o-nice-names] | -j[son] | -p[retty] }\n");
@@ -1061,8 +1061,11 @@ static void help(void)
 
 static int vdpa_cmd(struct vdpa *vdpa, int argc, char **argv)
 {
-	if (!argc || matches(*argv, "help") == 0) {
-		help();
+	if (!argc) {
+		help(stderr);
+		return -EINVAL;
+	} else if (matches(*argv, "help") == 0) {
+		help(stdout);
 		return 0;
 	} else if (matches(*argv, "mgmtdev") == 0) {
 		return cmd_mgmtdev(vdpa, argc - 1, argv + 1);
@@ -1127,6 +1130,7 @@ int main(int argc, char **argv)
 		{ NULL, 0, NULL, 0 }
 	};
 	struct vdpa *vdpa;
+	bool need_nl = true;
 	int opt;
 	int err;
 	int ret;
@@ -1150,12 +1154,12 @@ int main(int argc, char **argv)
 			pretty = true;
 			break;
 		case 'h':
-			help();
+			help(stdout);
 			ret = EXIT_SUCCESS;
 			goto vdpa_free;
 		default:
 			fprintf(stderr, "Unknown option.\n");
-			help();
+			help(stderr);
 			ret = EXIT_FAILURE;
 			goto vdpa_free;
 		}
@@ -1164,10 +1168,23 @@ int main(int argc, char **argv)
 	argc -= optind;
 	argv += optind;
 
-	err = vdpa_init(vdpa);
-	if (err) {
-		ret = EXIT_FAILURE;
-		goto vdpa_free;
+	if (argc > 0 && (strcmp(argv[0], "help") == 0 ||
+			 strcmp(argv[0], "-h") == 0 ||
+			 strcmp(argv[0], "--help") == 0))
+		need_nl = false;
+	
+	if (argc > 1 && (strcmp(argv[1], "help") == 0 ||
+			 strcmp(argv[1], "-h") == 0 ||
+			 strcmp(argv[1], "--help") == 0))
+		need_nl = false;
+
+
+	if (need_nl) {
+		err = vdpa_init(vdpa);
+		if (err) {
+			ret = EXIT_FAILURE;
+			goto vdpa_free;
+		}
 	}
 
 	err = vdpa_cmd(vdpa, argc, argv);
@@ -1179,7 +1196,8 @@ int main(int argc, char **argv)
 	ret = EXIT_SUCCESS;
 
 vdpa_fini:
-	vdpa_fini(vdpa);
+	if (need_nl)
+		vdpa_fini(vdpa);
 vdpa_free:
 	vdpa_free(vdpa);
 	return ret;
-- 
2.54.0


      parent reply	other threads:[~2026-06-23  1:17 UTC|newest]

Thread overview: 4+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-06-21 18:03 [PATCH iproute2] ip: return correct status from help command Rose Wright
2026-06-22 15:16 ` Stephen Hemminger
2026-06-22 17:18   ` Rose
2026-06-23  1:14   ` Rose Wright [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260623011454.214244-1-rosesophiewright@gmail.com \
    --to=rosesophiewright@gmail.com \
    --cc=netdev@vger.kernel.org \
    --cc=stephen@networkplumber.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox