From 7e099a6d54586754c05e0a3cfdc57244cdee93e6 Mon Sep 17 00:00:00 2001 From: Rob Landley Date: Wed, 23 Apr 2025 07:35:05 -0500 Subject: [PATCH] Add TOYFLAG_MOREHELP(CFG_BLAH) to allow annotated help text to drop out when a second config symbol isn't defined. Use this for various LSM -Z flags, PASSWD_SAD, sort -g, and wget's https support. This replaces the old help text merging scripts/config2help.c used to do. The annotation is a leading !, which removes the next char from usage: lines and the whole line from the rest of the help text. The ! is always removed, and the data it marks is only shown if the argument to TOYFLAG_MOREHELP() is true at compile time. In theory this plumbing should drop out when not used, like lib/args.c. --- lib/lib.c | 2 +- lib/toyflags.h | 7 +++-- main.c | 71 +++++++++++++++++++++++++++++++++----------- scripts/make.sh | 4 +-- toys.h | 2 +- toys/lsb/mknod.c | 22 +++++--------- toys/lsb/passwd.c | 4 ++- toys/net/wget.c | 3 +- toys/other/help.c | 6 ++-- toys/pending/route.c | 6 ++-- toys/posix/id.c | 14 ++------- toys/posix/mkdir.c | 18 +++-------- toys/posix/mkfifo.c | 19 ++++-------- toys/posix/sort.c | 27 ++++++----------- 14 files changed, 102 insertions(+), 103 deletions(-) diff --git a/lib/lib.c b/lib/lib.c index b4ff4f79..c3182dd7 100644 --- a/lib/lib.c +++ b/lib/lib.c @@ -72,7 +72,7 @@ void help_exit(char *msg, ...) { va_list va; - if (!msg) show_help(stdout, 1); + if (!msg) show_help(1); else { va_start(va, msg); verror_msg(msg, -1, va); diff --git a/lib/toyflags.h b/lib/toyflags.h index 9ef644ab..07e1d680 100644 --- a/lib/toyflags.h +++ b/lib/toyflags.h @@ -28,10 +28,13 @@ // Suppress default --help processing #define TOYFLAG_NOHELP (1<<9) #define TOYFLAG_AUTOCONF (1<<10) +#define TOYFLAG_TRIMHELP (1<<11) +#define TOYFLAG_BIGHELP (1<<12) +#define TOYFLAG_MOREHELP(x) (TOYFLAG_TRIMHELP|TOYFLAG_BIGHELP*(x)) // Line buffered stdout -#define TOYFLAG_LINEBUF (1<<11) -#define TOYFLAG_NOBUF (1<<12) +#define TOYFLAG_LINEBUF (1<<13) +#define TOYFLAG_NOBUF (1<<14) // Error code to return if argument parsing fails (default 1) #define TOYFLAG_ARGFAIL(x) (x<<24) diff --git a/main.c b/main.c index 69cc8afc..2ee701bf 100644 --- a/main.c +++ b/main.c @@ -49,7 +49,7 @@ struct toy_list *toy_find(char *name) // Figure out whether or not anything is using the option parsing logic, // because the compiler can't figure out whether or not to optimize it away -// on its' own. NEED_OPTIONS becomes a constant allowing if() to optimize +// on its' own. NEED_OPTIONS becomes a constant allowing if() to optimize // stuff out via dead code elimination. #undef NEWTOY @@ -60,6 +60,16 @@ static const int NEED_OPTIONS = #include "generated/newtoys.h" 0; // Ends the opts || opts || opts... +// Same trick but with the TRIMHELP plumbing. + +#undef NEWTOY +#undef OLDTOY +#define NEWTOY(name, opts, flags) ((flags)&TOYFLAG_TRIMHELP) || +#define OLDTOY(name, oldname, flags) ((flags)&TOYFLAG_TRIMHELP) || +static const int NEED_TRIMHELP = +#include "generated/newtoys.h" +0; + // Populate help text array #undef NEWTOY @@ -71,32 +81,31 @@ static const int NEED_OPTIONS = #define OLDTOY(name, oldname, flags) HELP_##oldname "\0" #endif +#if CFG_TOYBOX_ZHELP +#include "generated/zhelp.h" +static char *help_data = 0; +#else #include "generated/help.h" static const char help_data[] = #include "generated/newtoys.h" ; - -#if CFG_TOYBOX_ZHELP -#include "generated/zhelp.h" -#else -static char *zhelp_data = 0; +#define zhelp_data help_data #define ZHELP_LEN 0 #endif -void show_help(FILE *out, int flags) +void show_help(int flags) { int i = toys.which-toy_list; - char *s, *ss, *hd; + char *s, *ss; if (!CFG_TOYBOX_HELP) return; if (CFG_TOYBOX_ZHELP) - gunzip_mem(zhelp_data, sizeof(zhelp_data), hd = xmalloc(ZHELP_LEN), + gunzip_mem(zhelp_data, sizeof(zhelp_data), help_data = xmalloc(ZHELP_LEN), ZHELP_LEN); - else hd = (void *)help_data; - if (flags & HELP_HEADER) - fprintf(out, "Toybox %s"USE_TOYBOX(" multicall binary")"%s\n\n", + if (flags&HELP_HEADER) + printf("Toybox %s"USE_TOYBOX(" multicall binary")"%s\n\n", toybox_version, (CFG_TOYBOX && i) ? " (see toybox --help)" : " (see https://landley.net/toybox)"); @@ -107,18 +116,44 @@ void show_help(FILE *out, int flags) if (*s != 255) break; i = toy_find(++s)-toy_list; if ((flags & HELP_SEE) && toy_list[i].flags) { - if (flags & HELP_HTML) fprintf(out, "See %s\n", s, s); - else fprintf(out, "%s see %s\n", toys.which->name, s); + if (flags & HELP_HTML) printf("See %s\n", s, s); + else printf("%s see %s\n", toys.which->name, s); return; } } - if (!(flags & HELP_USAGE)) fprintf(out, "%s\n", s); - else { + // Only "help -u" calls HELP_USAGE + if (CFG_HELP && (flags&HELP_USAGE)) { strstart(&s, "usage: "); for (ss = s; *ss && *ss!='\n'; ss++); - fprintf(out, "%.*s\n", (int)(ss-s), s); + printf("%.*s\n", (int)(ss-s), s); + } else if (!NEED_TRIMHELP || !(toys.which->flags&TOYFLAG_TRIMHELP)) puts(s); + // TRIMHELP lines starting with ! are only displayed with BIGHELP, + // and the starting ! is edited out either way. + else { + int big = toys.which->flags&TOYFLAG_BIGHELP, usage = 1; + + for (; *s; s++) { + // For usage: line, chop out individual chars after each ! + if (usage && *s=='!') { + s++; + if (!big) continue; + } + putchar(*s); + + // For other lines, chop out whole lines starting with ! + if (*s=='\n') { + usage = 0; + if (s[1]=='!') { + s++; + if (big) continue; + s += strcspn(s, "\n"); + if (!*s) return; + } + } + } + putchar('\n'); } } @@ -142,7 +177,7 @@ void check_help(char **arg) toys.which = 0; if (!(toys.which = toy_find(arg[1]))) unknown(arg[1]); } - show_help(stdout, HELP_HEADER); + show_help(HELP_HEADER); xexit(); } diff --git a/scripts/make.sh b/scripts/make.sh index ea2b0daa..c9d5a327 100755 --- a/scripts/make.sh +++ b/scripts/make.sh @@ -150,8 +150,8 @@ fi # Rebuild config.h from .config $SED -En $KCONFIG_CONFIG > "$GENDIR"/config.h \ - -e 's/^# CONFIG_(.*) is not set.*/#define CFG_\1 0\n#define USE_\1(...)/p;t' \ - -e 's/^CONFIG_(.*)=y.*/#define CFG_\1 1\n#define USE_\1(...) __VA_ARGS__/p;t'\ + -e 's/^# CONFIG_(.*) is not set.*/#define CFG_\1 0\n#define USE_\1(...)\n#define SKIP_\1(...) __VA_ARGS__/p;t' \ + -e 's/^CONFIG_(.*)=y.*/#define CFG_\1 1\n#define USE_\1(...) __VA_ARGS__\n#define SKIP_\1(...)/p;t'\ -e 's/^CONFIG_(.*)=/#define CFG_\1 /p' || exit 1 # Process config.h and newtoys.h to generate FLAG_x macros. Note we must diff --git a/toys.h b/toys.h index 5ec71ce9..0e6ffa8c 100644 --- a/toys.h +++ b/toys.h @@ -89,7 +89,7 @@ #define HELP_HTML 8 // Output HTML struct toy_list *toy_find(char *name); -void show_help(FILE *out, int full); +void show_help(int full); void check_help(char **arg); void toy_singleinit(struct toy_list *which, char *argv[]); void toy_init(struct toy_list *which, char *argv[]); diff --git a/toys/lsb/mknod.c b/toys/lsb/mknod.c index cbc33980..7d3eabc3 100644 --- a/toys/lsb/mknod.c +++ b/toys/lsb/mknod.c @@ -4,27 +4,22 @@ * * http://refspecs.linuxfoundation.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/mknod.html -USE_MKNOD(NEWTOY(mknod, "<2>4m(mode):"USE_MKNOD_Z("Z:"), TOYFLAG_BIN|TOYFLAG_UMASK)) +USE_MKNOD(NEWTOY(mknod, "<2>4m(mode):"SKIP_TOYBOX_LSM_NONE("Z:"), TOYFLAG_BIN|TOYFLAG_UMASK|TOYFLAG_MOREHELP(!CFG_TOYBOX_LSM_NONE))) config MKNOD bool "mknod" default y help - usage: mknod [-m MODE] NAME TYPE [MAJOR MINOR] + usage: mknod [-m MODE] ![!-!Z! !C!O!N!T!E!X!T!]! NAME TYPE [MAJOR MINOR] - Create a special file NAME with a given type. TYPE is b for block device, - c or u for character device, p for named pipe (which ignores MAJOR/MINOR). + Create new device node NAME. TYPE is b for block device, c for character + device, p for named pipe (which ignores MAJOR/MINOR). -m Mode (file permissions) of new device, in octal or u+x format + !-Z Set security context of new device -config MKNOD_Z - bool - default y - depends on MKNOD && !TOYBOX_LSM_NONE - help - usage: mknod [-Z CONTEXT] ... - - -Z Set security context to created file + These days devtmpfs usually creates nodes for you. For the historical list, + See https://www.kernel.org/pub/linux/docs/lanana/device-list/devices-2.6.txt */ #define FOR_mknod @@ -49,8 +44,7 @@ void mknod_main(void) minor = atoi(toys.optargs[3]); } - if (FLAG(Z) && lsm_set_create(TT.Z)==-1) - perror_exit("-Z '%s' failed", TT.Z); + if (FLAG(Z) && lsm_set_create(TT.Z)==-1) perror_exit("-Z '%s' failed", TT.Z); if (mknod(*toys.optargs, mode|modes[type], dev_makedev(major, minor))) perror_exit_raw(*toys.optargs); } diff --git a/toys/lsb/passwd.c b/toys/lsb/passwd.c index 86921409..9cb41247 100644 --- a/toys/lsb/passwd.c +++ b/toys/lsb/passwd.c @@ -5,7 +5,7 @@ * * http://refspecs.linuxfoundation.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/passwd.html -USE_PASSWD(NEWTOY(passwd, ">1a:dlu", TOYFLAG_STAYROOT|TOYFLAG_USR|TOYFLAG_BIN)) +USE_PASSWD(NEWTOY(passwd, ">1a:dlu", TOYFLAG_STAYROOT|TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_MOREHELP(CFG_PASSWD_SAD))) config PASSWD bool "passwd" @@ -19,6 +19,8 @@ config PASSWD -d Set password to '' -l Lock (disable) account -u Unlock (enable) account + ! + !Checks password is >=6 chars, doesn't include username, and actually changed. config PASSWD_SAD bool "Add sad password checking heuristics" diff --git a/toys/net/wget.c b/toys/net/wget.c index 4e17309a..1b4df2bf 100644 --- a/toys/net/wget.c +++ b/toys/net/wget.c @@ -26,7 +26,7 @@ * TODO: Add support for Transfer Encoding (gzip|deflate) * TODO: Add support for RFC5987 -USE_WGET(NEWTOY(wget, "<1>1(max-redirect)#<0=20d(debug)O(output-document):p(post-data):", TOYFLAG_USR|TOYFLAG_BIN)) +USE_WGET(NEWTOY(wget, "<1>1(max-redirect)#<0=20d(debug)O(output-document):p(post-data):", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_MOREHELP(CFG_WGET_LIBTLS|CFG_TOYBOX_LIBCRYPTO))) config WGET bool "wget" @@ -40,6 +40,7 @@ config WGET examples: wget http://www.example.com + ! wget https://www.example.com config WGET_LIBTLS bool "Enable HTTPS support for wget via LibTLS" diff --git a/toys/other/help.c b/toys/other/help.c index 18fbed0d..64f6a029 100644 --- a/toys/other/help.c +++ b/toys/other/help.c @@ -30,12 +30,12 @@ static void do_help(struct toy_list *t) xprintf("

%s

\n", t->name, t->name);
 
   toys.which = t;
-  show_help(stdout, HELP_USAGE*FLAG(u) + (HELP_SEE|HELP_HTML)*FLAG(h));
+  show_help(HELP_USAGE*FLAG(u) + (HELP_SEE|HELP_HTML)*FLAG(h));
 
   if (FLAG(h)) xprintf("
\n"); } -// Simple help is just toys.which = toy_find("name"); show_help(stdout, 0); +// Simple help is just toys.which = toy_find("name"); show_help(0); // but iterating through html output and all commands is a bit more void help_main(void) @@ -47,7 +47,7 @@ void help_main(void) for (i = 0; i < toys.toycount; i++) { if (!(toy_list[i].flags&(TOYFLAG_NOFORK|TOYFLAG_MAYFORK))) continue; toys.which = toy_list+i; - show_help(stdout, HELP_SEE|HELP_USAGE*!toys.optflags); + show_help(HELP_SEE|HELP_USAGE*!toys.optflags); } return; } diff --git a/toys/pending/route.c b/toys/pending/route.c index 3b0e5be2..0308c928 100644 --- a/toys/pending/route.c +++ b/toys/pending/route.c @@ -300,11 +300,11 @@ static void setroute(sa_family_t f, char **argv) else if (!strcmp(*argv, "reinstate")) continue; else if (!strcmp(*argv, "reject")) rtMsg->rtm_type = RTN_UNREACHABLE; else { - if (!argv[1]) show_help(stdout, 1); + if (!argv[1]) help_exit("need arg"); if (!strcmp(*argv, "metric")) { unsigned int priority = atolx_range(argv[1], 0, UINT_MAX); - addAttr(nlMsg, sizeof(toybuf), &priority, RTA_PRIORITY, sizeof(unsigned int)); + addAttr(nlMsg, sizeof(toybuf), &priority, RTA_PRIORITY, 4); } else if (!strcmp(*argv, "netmask")) { uint32_t netmask; char *ptr; @@ -368,7 +368,7 @@ void route_main(void) if (!*toys.optargs) { if (!TT.A || !strcmp(TT.A, "inet")) display_routes(AF_INET); else if (!strcmp(TT.A, "inet6")) display_routes(AF_INET6); - else show_help(stdout, 1); + else help_exit("bad -A '%s'", TT.A); } else { if (!TT.A) { if (toys.optc>1 && strchr(toys.optargs[1], ':')) { diff --git a/toys/posix/id.c b/toys/posix/id.c index 473c6b23..f311920b 100644 --- a/toys/posix/id.c +++ b/toys/posix/id.c @@ -6,7 +6,7 @@ * * See http://opengroup.org/onlinepubs/9699919799/utilities/id.html -USE_ID(NEWTOY(id, ">1"USE_ID_Z("Z")"nGgru[!"USE_ID_Z("Z")"Ggu]", TOYFLAG_USR|TOYFLAG_BIN)) +USE_ID(NEWTOY(id, ">1"SKIP_TOYBOX_LSM_NONE("Z")"nGgru[!"SKIP_TOYBOX_LSM_NONE("Z")"Ggu]", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_MOREHELP(!CFG_TOYBOX_LSM_NONE))) USE_GROUPS(NEWTOY(groups, NULL, TOYFLAG_USR|TOYFLAG_BIN)) USE_LOGNAME(NEWTOY(logname, ">0", TOYFLAG_USR|TOYFLAG_BIN)) USE_WHOAMI(OLDTOY(whoami, logname, TOYFLAG_USR|TOYFLAG_BIN)) @@ -15,7 +15,7 @@ config ID bool "id" default y help - usage: id [-Ggnru] [USER...] + usage: id [-Ggnru!Z] [USER...] Print user and group ID. @@ -24,15 +24,7 @@ config ID -n Print names instead of numeric IDs (to be used with -Ggu) -r Show real ID instead of effective ID -u Show only the effective user ID - -config ID_Z - bool - default y - depends on ID && !TOYBOX_LSM_NONE - help - usage: id [-Z] - - -Z Show only security context + !-Z Show only security context config GROUPS bool "groups" diff --git a/toys/posix/mkdir.c b/toys/posix/mkdir.c index df9061d7..c883a93e 100644 --- a/toys/posix/mkdir.c +++ b/toys/posix/mkdir.c @@ -4,28 +4,20 @@ * * See http://opengroup.org/onlinepubs/9699919799/utilities/mkdir.html -USE_MKDIR(NEWTOY(mkdir, "<1"USE_MKDIR_Z("Z:")"vp(parent)(parents)m:", TOYFLAG_BIN|TOYFLAG_UMASK)) +USE_MKDIR(NEWTOY(mkdir, "<1"SKIP_TOYBOX_LSM_NONE("Z:")"vp(parent)(parents)m:", TOYFLAG_BIN|TOYFLAG_UMASK|TOYFLAG_MOREHELP(!CFG_TOYBOX_LSM_NONE))) config MKDIR bool "mkdir" default y help - usage: mkdir [-vp] [-m MODE] [DIR...] + usage: mkdir [-vp] ![!-!Z! !c!o!n!t!e!x!t!]! [-m MODE] [DIR...] Create one or more directories. -m Set permissions of directory to mode -p Make parent directories as needed -v Verbose - -config MKDIR_Z - bool - default y - depends on MKDIR && !TOYBOX_LSM_NONE - help - usage: [-Z context] - - -Z Set security context + !-Z Set security context */ #define FOR_mkdir @@ -40,9 +32,7 @@ void mkdir_main(void) char **s; mode_t mode = (0777&~toys.old_umask); - if (CFG_MKDIR_Z && FLAG(Z)) - if (0>lsm_set_create(TT.Z)) - perror_exit("-Z '%s' failed", TT.Z); + if (FLAG(Z)) if (0>lsm_set_create(TT.Z)) perror_exit("-Z '%s' failed", TT.Z); if (TT.m) mode = string_to_mode(TT.m, 0777); diff --git a/toys/posix/mkfifo.c b/toys/posix/mkfifo.c index 366ac436..3d412ccf 100644 --- a/toys/posix/mkfifo.c +++ b/toys/posix/mkfifo.c @@ -4,24 +4,17 @@ * * See http://opengroup.org/onlinepubs/9699919799/utilities/mkfifo.html -USE_MKFIFO(NEWTOY(mkfifo, "<1"USE_MKFIFO_Z("Z:")"m:", TOYFLAG_USR|TOYFLAG_BIN)) +USE_MKFIFO(NEWTOY(mkfifo, "<1"SKIP_TOYBOX_LSM_NONE("Z:")"m:", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_MOREHELP(!CFG_TOYBOX_LSM_NONE))) config MKFIFO bool "mkfifo" default y help - usage: mkfifo [NAME...] + usage: mkfifo ![!-!Z! !C!O!N!T!E!X!T!]! [NAME...] Create FIFOs (named pipes). - -config MKFIFO_Z - bool - default y - depends on MKFIFO && !TOYBOX_LSM_NONE - help - usage: mkfifo [-Z CONTEXT] - - -Z Security context + ! + !-Z Security context */ #define FOR_mkfifo @@ -40,9 +33,7 @@ void mkfifo_main(void) TT.mode = 0666; if (FLAG(m)) TT.mode = string_to_mode(TT.m, 0); - if (CFG_MKFIFO_Z && FLAG(Z)) - if (0>lsm_set_create(TT.Z)) - perror_exit("-Z '%s' failed", TT.Z); + if (FLAG(Z)) if (0>lsm_set_create(TT.Z)) perror_exit("-Z '%s' failed", TT.Z); for (s = toys.optargs; *s; s++) if (mknod(*s, S_IFIFO | TT.mode, 0) < 0) perror_msg_raw(*s); diff --git a/toys/posix/sort.c b/toys/posix/sort.c index d69b6224..55c843ca 100644 --- a/toys/posix/sort.c +++ b/toys/posix/sort.c @@ -7,51 +7,42 @@ * Deviations from POSIX: Lots. * We invented -x -USE_SORT(NEWTOY(sort, USE_SORT_FLOAT("g")"S:T:m" "o:k*t:" "xVbMCcszdfirun", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_ARGFAIL(2))) +USE_SORT(NEWTOY(sort, USE_TOYBOX_FLOAT("g")"S:T:m" "o:k*t:" "xVbMCcszdfirun", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_ARGFAIL(2)|TOYFLAG_MOREHELP(CFG_TOYBOX_FLOAT))) config SORT bool "sort" default y help - usage: sort [-runbCcdfiMsxVz] [FILE...] [-k#[,#[x]] [-t X]] [-o FILE] + usage: sort [-bCcdf!giMnrsuxVz] [FILE...] [-k#[,#[x]] [-t X]] [-o FILE] Sort all lines of text from input files (or stdin) to stdout. - -r Reverse - -u Unique lines only - -n Numeric order (instead of alphabetical) - -b Ignore leading blanks (or trailing blanks in second part of key) -C Check whether input is sorted -c Warn if input is unsorted -d Dictionary order (use alphanumeric and whitespace chars only) -f Force uppercase (case insensitive sort) + !-g General numeric sort (double precision with nan and inf) -i Ignore nonprinting characters - -k Sort by "key" (see below) + -k Sort by KEY (see below) -M Month sort (jan, feb, etc) + -n Numeric order (instead of alphabetical) -o Output to FILE instead of stdout + -r Reverse -s Skip fallback sort (only sort with keys) -t Use a key separator other than whitespace + -u Unique lines only -x Hexadecimal numerical sort -V Version numbers (name-1.234-rc6.5b.tgz) -z Zero (null) terminated lines - Sorting by key looks at a subset of the words on each line. -k2 uses the + Sorting by KEY looks at a subset of the words on each line. -k2 uses the second word to the end of the line, -k2,2 looks at only the second word, -k2,4 looks from the start of the second to the end of the fourth word. -k2.4,5 starts from the fourth character of the second word, to the end of the fifth word. Negative values count from the end. Specifying multiple keys uses the later keys as tie breakers, in order. A type specifier appended to a sort key (such as -2,2n) applies only to sorting that key. - -config SORT_FLOAT - bool - default y - depends on TOYBOX_FLOAT - help - usage: sort [-g] - - -g General numeric sort (double precision with nan and inf) */ #define FOR_sort @@ -179,7 +170,7 @@ static struct sort_key *add_key(void) // Perform actual comparison static int compare_values(int flags, char *x, char *y) { - if (CFG_SORT_FLOAT && (flags & FLAG_g)) { + if (CFG_TOYBOX_FLOAT && (flags & FLAG_g)) { char *xx,*yy; double dx = strtod(x,&xx), dy = strtod(y,&yy); int xinf, yinf; -- 2.39.5