From 3af63683fe07465fd96827cab0d946cbdaba6f73 Mon Sep 17 00:00:00 2001 From: Rob N Date: Wed, 20 Sep 2023 02:06:47 +1000 Subject: [PATCH] cmd: add 'help' subcommand to zpool and zfs 'program help subcommand' is a reasonably common pattern for multifunction command-line programs. This commit adds support for that style to the zpool and zfs commands. When run as 'zpool help []' or 'zfs help []', executes the 'man' program on the PATH with the most likely manpage name for the requested topic: "zpool-" or "zfs-" for subcommands, or "zpool" or "zfs" for the "concepts" and "props" topics. If no topic is supplied, uses the top "zpool" or "zfs" pages. Reviewed-by: Brian Behlendorf Reviewed-by: Kay Pedersen Signed-off-by: Rob Norris Closes #15288 --- cmd/zfs/zfs_main.c | 30 ++++++++++++++++++ cmd/zpool/zpool_main.c | 31 +++++++++++++++++++ .../cli_root/zfs_program/zfs_program_json.ksh | 4 ++- 3 files changed, 64 insertions(+), 1 deletion(-) diff --git a/cmd/zfs/zfs_main.c b/cmd/zfs/zfs_main.c index 5ed25d1ea..62802de23 100644 --- a/cmd/zfs/zfs_main.c +++ b/cmd/zfs/zfs_main.c @@ -132,6 +132,8 @@ static int zfs_do_zone(int argc, char **argv); static int zfs_do_unzone(int argc, char **argv); #endif +static int zfs_do_help(int argc, char **argv); + /* * Enable a reasonable set of defaults for libumem debugging on DEBUG builds. */ @@ -606,6 +608,9 @@ usage(boolean_t requested) (void) fprintf(fp, gettext("\nFor the delegated permission list, run: %s\n"), "zfs allow|unallow"); + (void) fprintf(fp, + gettext("\nFor further help on a command or topic, " + "run: %s\n"), "zfs help []"); } /* @@ -8726,6 +8731,25 @@ zfs_do_version(int argc, char **argv) return (zfs_version_print() != 0); } +/* Display documentation */ +static int +zfs_do_help(int argc, char **argv) +{ + char page[MAXNAMELEN]; + if (argc < 3 || strcmp(argv[2], "zfs") == 0) + strcpy(page, "zfs"); + else if (strcmp(argv[2], "concepts") == 0 || + strcmp(argv[2], "props") == 0) + snprintf(page, sizeof (page), "zfs%s", argv[2]); + else + snprintf(page, sizeof (page), "zfs-%s", argv[2]); + + execlp("man", "man", page, NULL); + + fprintf(stderr, "couldn't run man program: %s", strerror(errno)); + return (-1); +} + int main(int argc, char **argv) { @@ -8781,6 +8805,12 @@ main(int argc, char **argv) if ((strcmp(cmdname, "-V") == 0) || (strcmp(cmdname, "--version") == 0)) return (zfs_do_version(argc, argv)); + /* + * Special case 'help' + */ + if (strcmp(cmdname, "help") == 0) + return (zfs_do_help(argc, argv)); + if ((g_zfs = libzfs_init()) == NULL) { (void) fprintf(stderr, "%s\n", libzfs_error_init(errno)); return (1); diff --git a/cmd/zpool/zpool_main.c b/cmd/zpool/zpool_main.c index 6d0dae8d8..d64fdfa5b 100644 --- a/cmd/zpool/zpool_main.c +++ b/cmd/zpool/zpool_main.c @@ -126,6 +126,8 @@ static int zpool_do_version(int, char **); static int zpool_do_wait(int, char **); +static int zpool_do_help(int argc, char **argv); + static zpool_compat_status_t zpool_do_load_compat( const char *, boolean_t *); @@ -538,6 +540,10 @@ usage(boolean_t requested) (void) fprintf(fp, "%s", get_usage(command_table[i].usage)); } + + (void) fprintf(fp, + gettext("\nFor further help on a command or topic, " + "run: %s\n"), "zpool help []"); } else { (void) fprintf(fp, gettext("usage:\n")); (void) fprintf(fp, "%s", get_usage(current_command->usage)); @@ -11051,6 +11057,25 @@ zpool_do_version(int argc, char **argv) return (zfs_version_print() != 0); } +/* Display documentation */ +static int +zpool_do_help(int argc, char **argv) +{ + char page[MAXNAMELEN]; + if (argc < 3 || strcmp(argv[2], "zpool") == 0) + strcpy(page, "zpool"); + else if (strcmp(argv[2], "concepts") == 0 || + strcmp(argv[2], "props") == 0) + snprintf(page, sizeof (page), "zpool%s", argv[2]); + else + snprintf(page, sizeof (page), "zpool-%s", argv[2]); + + execlp("man", "man", page, NULL); + + fprintf(stderr, "couldn't run man program: %s", strerror(errno)); + return (-1); +} + /* * Do zpool_load_compat() and print error message on failure */ @@ -11118,6 +11143,12 @@ main(int argc, char **argv) if ((strcmp(cmdname, "-V") == 0) || (strcmp(cmdname, "--version") == 0)) return (zpool_do_version(argc, argv)); + /* + * Special case 'help' + */ + if (strcmp(cmdname, "help") == 0) + return (zpool_do_help(argc, argv)); + if ((g_zfs = libzfs_init()) == NULL) { (void) fprintf(stderr, "%s\n", libzfs_error_init(errno)); return (1); diff --git a/tests/zfs-tests/tests/functional/cli_root/zfs_program/zfs_program_json.ksh b/tests/zfs-tests/tests/functional/cli_root/zfs_program/zfs_program_json.ksh index b0265c5ee..2241b77bf 100755 --- a/tests/zfs-tests/tests/functional/cli_root/zfs_program/zfs_program_json.ksh +++ b/tests/zfs-tests/tests/functional/cli_root/zfs_program/zfs_program_json.ksh @@ -117,7 +117,9 @@ usage: For the property list, run: zfs set|get -For the delegated permission list, run: zfs allow|unallow") +For the delegated permission list, run: zfs allow|unallow + +For further help on a command or topic, run: zfs help []") cnt=0 for cmd in ${neg_cmds[@]}; do log_mustnot zfs program $cmd $TESTPOOL $TESTZCP $TESTDS 2>&1