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.
This commit is contained in:
Rob Landley 2025-04-23 07:35:05 -05:00
parent ab1de187e2
commit 7e099a6d54
14 changed files with 102 additions and 103 deletions

View File

@ -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);

View File

@ -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)

71
main.c
View File

@ -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 <a href=#%s>%s</a>\n", s, s);
else fprintf(out, "%s see %s\n", toys.which->name, s);
if (flags & HELP_HTML) printf("See <a href=#%s>%s</a>\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();
}

View File

@ -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

2
toys.h
View File

@ -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[]);

View File

@ -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);
}

View File

@ -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"

View File

@ -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"

View File

@ -30,12 +30,12 @@ static void do_help(struct toy_list *t)
xprintf("<a name=\"%s\"><h1>%s</h1><blockquote><pre>\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("</blockquote></pre>\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;
}

View File

@ -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], ':')) {

View File

@ -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"

View File

@ -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);

View File

@ -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);

View File

@ -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;