diff -Nru toybox-0.8.8+dfsg/configure toybox-0.8.9+dfsg/configure --- toybox-0.8.8+dfsg/configure 2022-08-12 07:58:03.000000000 +0000 +++ toybox-0.8.9+dfsg/configure 2023-01-10 19:24:45.000000000 +0000 @@ -13,7 +13,7 @@ # Warn about stuff, disable stupid warnings, be 8-bit clean for utf8. [ "${CFLAGS/-funsigned-char//}" == "$CFLAGS" ] && - CFLAGS+=" -Wall -Wundef -Werror=implicit-function-declaration -Wno-char-subscripts -Wno-pointer-sign -Wno-string-plus-int -funsigned-char" + CFLAGS+=" -Wall -Wundef -Werror=implicit-function-declaration -Wno-char-subscripts -Wno-pointer-sign -funsigned-char" # Set default values if variable not already set : ${CC:=cc} ${HOSTCC:=cc} ${GENDIR:=generated} ${KCONFIG_CONFIG:=.config} diff -Nru toybox-0.8.8+dfsg/debian/changelog toybox-0.8.9+dfsg/debian/changelog --- toybox-0.8.8+dfsg/debian/changelog 2022-09-11 22:27:42.000000000 +0000 +++ toybox-0.8.9+dfsg/debian/changelog 2023-04-12 01:58:55.000000000 +0000 @@ -1,3 +1,19 @@ +toybox (0.8.9+dfsg-1ubuntu1) lunar; urgency=medium + + * Clear V from the environment, the package fails to build if set. + + -- Steve Langasek Wed, 12 Apr 2023 01:58:55 +0000 + +toybox (0.8.9+dfsg-1) unstable; urgency=medium + + * New upstream version 0.8.9+dfsg + * Bump Standards-Version to 4.6.2 (no changes) + * Drop patch set-pathmax-default.patch + * Refresh patches + * Autopkgtest: Update toybox-selftest + + -- Antoni Villalonga Sat, 18 Feb 2023 19:07:34 +0000 + toybox (0.8.8+dfsg-2) unstable; urgency=medium * New patch timeout.patch. Closes: #1019392 diff -Nru toybox-0.8.8+dfsg/debian/control toybox-0.8.9+dfsg/debian/control --- toybox-0.8.8+dfsg/debian/control 2022-09-11 22:27:42.000000000 +0000 +++ toybox-0.8.9+dfsg/debian/control 2023-04-12 01:58:55.000000000 +0000 @@ -1,13 +1,14 @@ Source: toybox Section: utils Priority: optional -Maintainer: Antoni Villalonga +Maintainer: Ubuntu Developers +XSBC-Original-Maintainer: Antoni Villalonga Build-Depends: debhelper-compat (= 13), # libssl-dev, # zlib1g-dev, help2man, locales-all, -Standards-Version: 4.6.1.0 +Standards-Version: 4.6.2 Homepage: https://www.landley.net/toybox/ Vcs-Browser: https://salsa.debian.org/debian/toybox Vcs-Git: https://salsa.debian.org/debian/toybox.git diff -Nru toybox-0.8.8+dfsg/debian/patches/debianize-paths.patch toybox-0.8.9+dfsg/debian/patches/debianize-paths.patch --- toybox-0.8.8+dfsg/debian/patches/debianize-paths.patch 2022-09-11 22:27:42.000000000 +0000 +++ toybox-0.8.9+dfsg/debian/patches/debianize-paths.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,50 +0,0 @@ ---- a/toys/other/devmem.c -+++ b/toys/other/devmem.c -@@ -2,7 +2,7 @@ - * - * Copyright 2019 The Android Open Source Project - --USE_DEVMEM(NEWTOY(devmem, "<1>3", TOYFLAG_USR|TOYFLAG_BIN)) -+USE_DEVMEM(NEWTOY(devmem, "<1>3", TOYFLAG_USR|TOYFLAG_SBIN)) - - config DEVMEM - bool "devmem" ---- a/toys/other/i2ctools.c -+++ b/toys/other/i2ctools.c -@@ -11,10 +11,10 @@ - * TODO: i2cget non-byte modes? default to current read address? - * TODO: i2cset -r? -m MASK? c/s modes, p mode modifier? - --USE_I2CDETECT(NEWTOY(i2cdetect, ">3aFlqry[!qr]", TOYFLAG_USR|TOYFLAG_BIN)) --USE_I2CDUMP(NEWTOY(i2cdump, "<2>2fy", TOYFLAG_USR|TOYFLAG_BIN)) --USE_I2CGET(NEWTOY(i2cget, "<3>3fy", TOYFLAG_USR|TOYFLAG_BIN)) --USE_I2CSET(NEWTOY(i2cset, "<4fy", TOYFLAG_USR|TOYFLAG_BIN)) -+USE_I2CDETECT(NEWTOY(i2cdetect, ">3aFlqry[!qr]", TOYFLAG_USR|TOYFLAG_SBIN)) -+USE_I2CDUMP(NEWTOY(i2cdump, "<2>2fy", TOYFLAG_USR|TOYFLAG_SBIN)) -+USE_I2CGET(NEWTOY(i2cget, "<3>3fy", TOYFLAG_USR|TOYFLAG_SBIN)) -+USE_I2CSET(NEWTOY(i2cset, "<4fy", TOYFLAG_USR|TOYFLAG_SBIN)) - - config I2CDETECT - bool "i2cdetect" ---- a/toys/other/watchdog.c -+++ b/toys/other/watchdog.c -@@ -4,7 +4,7 @@ - * - * See kernel.org/doc/Documentation/watchdog/watchdog-api.txt - --USE_WATCHDOG(NEWTOY(watchdog, "<1>1Ft#=4<1T#=60<1", TOYFLAG_NEEDROOT|TOYFLAG_BIN)) -+USE_WATCHDOG(NEWTOY(watchdog, "<1>1Ft#=4<1T#=60<1", TOYFLAG_NEEDROOT|TOYFLAG_SBIN)) - - config WATCHDOG - bool "watchdog" ---- a/toys/posix/df.c -+++ b/toys/posix/df.c -@@ -4,7 +4,7 @@ - * - * See http://opengroup.org/onlinepubs/9699919799/utilities/df.html - --USE_DF(NEWTOY(df, "HPkhit*a[-HPh]", TOYFLAG_SBIN)) -+USE_DF(NEWTOY(df, "HPkhit*a[-HPh]", TOYFLAG_BIN)) - - config DF - bool "df" diff -Nru toybox-0.8.8+dfsg/debian/patches/series toybox-0.8.9+dfsg/debian/patches/series --- toybox-0.8.8+dfsg/debian/patches/series 2022-09-11 22:27:42.000000000 +0000 +++ toybox-0.8.9+dfsg/debian/patches/series 2023-02-18 19:07:34.000000000 +0000 @@ -1,6 +1,2 @@ silent-blhc.patch tests-remove-binary-files.patch -debianize-paths.patch -spelling.patch -timeout.patch -set-pathmax-default.patch diff -Nru toybox-0.8.8+dfsg/debian/patches/set-pathmax-default.patch toybox-0.8.9+dfsg/debian/patches/set-pathmax-default.patch --- toybox-0.8.8+dfsg/debian/patches/set-pathmax-default.patch 2022-09-11 22:27:42.000000000 +0000 +++ toybox-0.8.9+dfsg/debian/patches/set-pathmax-default.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,65 +0,0 @@ ---- a/kconfig/confdata.c -+++ b/kconfig/confdata.c -@@ -31,6 +31,10 @@ - #define CONFIG_PREFIX "CONFIG_" - #endif - -+#ifndef PATH_MAX -+#define PATH_MAX 4096 /* # chars in a path name including nul */ -+#endif -+ - static void conf_warning(const char *fmt, ...) - { - va_list ap; ---- a/kconfig/lex.zconf.c_shipped -+++ b/kconfig/lex.zconf.c_shipped -@@ -765,6 +765,10 @@ - - #define START_STRSIZE 16 - -+#ifndef PATH_MAX -+#define PATH_MAX 4096 /* # chars in a path name including nul */ -+#endif -+ - static struct { - struct file *file; - int lineno; ---- a/kconfig/mconf.c -+++ b/kconfig/mconf.c -@@ -26,6 +26,10 @@ - #include "lkc.h" - #include "lxdialog/dialog.h" - -+#ifndef PATH_MAX -+#define PATH_MAX 4096 /* # chars in a path name including nul */ -+#endif -+ - static char menu_backtitle[128]; - static const char mconf_readme[] = N_( - "Overview\n" ---- a/toys.h -+++ b/toys.h -@@ -45,6 +45,10 @@ - #include - #include - -+#ifndef PATH_MAX -+#define PATH_MAX 4096 /* # chars in a path name including nul */ -+#endif -+ - // Posix networking - - #include ---- a/toys/posix/getconf.c -+++ b/toys/posix/getconf.c -@@ -46,6 +46,10 @@ - #define _CS_V7_ENV -1 - #endif - -+#ifndef PATH_MAX -+#define PATH_MAX 4096 /* # chars in a path name including nul */ -+#endif -+ - struct config { - char *name; - long long value; diff -Nru toybox-0.8.8+dfsg/debian/patches/spelling.patch toybox-0.8.9+dfsg/debian/patches/spelling.patch --- toybox-0.8.8+dfsg/debian/patches/spelling.patch 2022-09-11 22:27:42.000000000 +0000 +++ toybox-0.8.9+dfsg/debian/patches/spelling.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,22 +0,0 @@ ---- a/toys/other/nsenter.c -+++ b/toys/other/nsenter.c -@@ -40,7 +40,7 @@ - - Each namespace can take an optional argument, a persistent mountpoint usable - by the nsenter command to add new processes to that the namespace. (Specify -- multiple namespaces to unshare seperately, ala -c -i -m because -cim is -c -+ multiple namespaces to unshare separately, ala -c -i -m because -cim is -c - with persistent mount "im".) - - config NSENTER ---- a/toys/posix/find.c -+++ b/toys/posix/find.c -@@ -617,7 +617,7 @@ - ff = 0; - ch = *fmt; - -- // long long is its own stack size on LP64, so handle seperately -+ // long long is its own stack size on LP64, so handle separately - if (ch == 'i' || ch == 's') { - strcpy(next+len, "lld"); - printf(next, (ch == 'i') ? (long long)new->st.st_ino diff -Nru toybox-0.8.8+dfsg/debian/patches/timeout.patch toybox-0.8.9+dfsg/debian/patches/timeout.patch --- toybox-0.8.8+dfsg/debian/patches/timeout.patch 2022-09-11 22:27:42.000000000 +0000 +++ toybox-0.8.9+dfsg/debian/patches/timeout.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,85 +0,0 @@ -Description: Fix timeout -Author: Antoni Villalonga i Noceras -Forwarded: no-needed -Last-Update: Tue, 13 Sep 2022 06:09:10 +0000 - -Cherry-picked fix from upstream: - https://github.com/landley/toybox/commit/6b78f8de9edab04b520e6c6ed39bf7b36021d146 - https://github.com/landley/toybox/commit/e4103b8183cfb9992badd3fbc9827e9bc4a74c38 - -Avoids usage of non-volatile local variables that their values are changed -between setjmp() and longjmp(). Man: SETJMP(3). -Before that change pid and fds local variables values are unspecified. Now it's -fixed by using TT global. - -Credits to upstream: 'enh-google' and 'landley'. ---- a/toys/other/timeout.c -+++ b/toys/other/timeout.c -@@ -34,6 +34,7 @@ - - struct pollfd pfd; - sigjmp_buf sj; -+ int fds[2], pid; - ) - - static void handler(int sig) -@@ -48,7 +49,7 @@ - - void timeout_main(void) - { -- int fds[] = {0, -1}, ii, ms, nextsig, pid; -+ int ii, ms, nextsig; - struct timespec tts, kts; - - // Use same ARGFAIL value for any remaining parsing errors -@@ -57,18 +58,17 @@ - if (TT.k) xparsetimespec(TT.k, &kts); - - nextsig = SIGTERM; -- if (TT.s && -1 == (nextsig = sig_to_num(TT.s))) -- error_exit("bad -s: '%s'", TT.s); -- -+ if (TT.s && -1==(nextsig = sig_to_num(TT.s))) error_exit("bad -s: '%s'",TT.s); - if (!FLAG(foreground)) setpgid(0, 0); - - toys.exitval = 0; - TT.pfd.events = POLLIN; -+ TT.fds[1] = -1; - if (sigsetjmp(TT.sj, 1)) goto done; - xsignal_flags(SIGCHLD, handler, SA_NOCLDSTOP); -- pid = xpopen_both(toys.optargs+1, FLAG(i) ? fds : 0); -- if (!FLAG(i)) xpipe(fds); -- TT.pfd.fd = fds[1]; -+ TT.pid = xpopen_both(toys.optargs+1, FLAG(i) ? TT.fds : 0); -+ if (!FLAG(i)) xpipe(TT.fds); -+ TT.pfd.fd = TT.fds[1]; - ms = nantomil(&tts); - for (;;) { - if (1 != xpoll(&TT.pfd, 1, ms)) { -@@ -76,7 +76,7 @@ - perror_msg("sending signal %s to command %s", num_to_sig(nextsig), - toys.optargs[1]); - toys.exitval = (nextsig==9) ? 137 : 124; -- kill(pid, nextsig); -+ kill(TT.pid, nextsig); - if (!TT.k || nextsig==SIGKILL) break; - nextsig = SIGKILL; - ms = nantomil(&kts); -@@ -85,7 +85,7 @@ - } - if (TT.pfd.revents&POLLIN) { - errno = 0; -- if (1>(ii = read(fds[1], toybuf, sizeof(toybuf)))) { -+ if (1>(ii = read(TT.fds[1], toybuf, sizeof(toybuf)))) { - if (errno==EINTR) continue; - break; - } -@@ -95,7 +95,7 @@ - } - done: - xsignal(SIGCHLD, SIG_DFL); -- ii = xpclose_both(pid, fds); -+ ii = xpclose_both(TT.pid, TT.fds); - - if (FLAG(preserve_status) || !toys.exitval) toys.exitval = ii; - } diff -Nru toybox-0.8.8+dfsg/debian/rules toybox-0.8.9+dfsg/debian/rules --- toybox-0.8.8+dfsg/debian/rules 2022-09-11 22:27:42.000000000 +0000 +++ toybox-0.8.9+dfsg/debian/rules 2023-04-12 01:56:39.000000000 +0000 @@ -4,6 +4,8 @@ DPKG_EXPORT_BUILDFLAGS = 1 include /usr/share/dpkg/buildflags.mk +export V= + %: dh $@ diff -Nru toybox-0.8.8+dfsg/debian/tests/toybox-selftest toybox-0.8.9+dfsg/debian/tests/toybox-selftest --- toybox-0.8.8+dfsg/debian/tests/toybox-selftest 2022-09-11 22:27:42.000000000 +0000 +++ toybox-0.8.9+dfsg/debian/tests/toybox-selftest 2023-02-18 19:07:34.000000000 +0000 @@ -1,5 +1,9 @@ #!/usr/bin/env bash +set -x +set -e + make -f debian/rules override_dh_auto_configure -cp /bin/toybox toybox +make +cp -vf /bin/toybox toybox make tests diff -Nru toybox-0.8.8+dfsg/kconfig/freebsd_miniconfig toybox-0.8.9+dfsg/kconfig/freebsd_miniconfig --- toybox-0.8.8+dfsg/kconfig/freebsd_miniconfig 2022-08-12 07:58:03.000000000 +0000 +++ toybox-0.8.9+dfsg/kconfig/freebsd_miniconfig 2023-01-10 19:24:45.000000000 +0000 @@ -11,14 +11,18 @@ CONFIG_CPIO=y CONFIG_CUT=y CONFIG_DATE=y +CONFIG_DF=y CONFIG_DIRNAME=y CONFIG_DU=y CONFIG_ECHO=y +CONFIG_ENV=y CONFIG_EXPAND=y CONFIG_FALSE=y CONFIG_FILE=y CONFIG_FIND=y CONFIG_GREP=y +CONFIG_EGREP=y +CONFIG_FGREP=y CONFIG_HEAD=y CONFIG_ICONV=y CONFIG_ID=y @@ -49,13 +53,13 @@ CONFIG_SORT=y CONFIG_SPLIT=y CONFIG_STRINGS=y +CONFIG_TAIL=y CONFIG_TEE=y CONFIG_TEST=y CONFIG_TIME=y CONFIG_TOUCH=y CONFIG_TRUE=y CONFIG_TTY=y -CONFIG_UNAME=y CONFIG_UNIQ=y CONFIG_UNLINK=y CONFIG_UUDECODE=y @@ -65,14 +69,16 @@ CONFIG_XARGS=y CONFIG_ACPI=y CONFIG_ASCII=y +CONFIG_UNICODE=y CONFIG_BASE64=y +CONFIG_BASE32=y CONFIG_BUNZIP2=y CONFIG_BZCAT=y CONFIG_CHROOT=y CONFIG_CHRT=y -CONFIG_CHVT=y CONFIG_CLEAR=y CONFIG_COUNT=y +CONFIG_DEVMEM=y CONFIG_DOS2UNIX=y CONFIG_UNIX2DOS=y CONFIG_FACTOR=y @@ -86,6 +92,7 @@ CONFIG_LSPCI=y CONFIG_LSUSB=y CONFIG_MAKEDEVS=y +CONFIG_MCOOKIE=y CONFIG_MKPASSWD=y CONFIG_MKSWAP=y CONFIG_MODINFO=y @@ -93,12 +100,16 @@ CONFIG_PMAP=y CONFIG_PRINTENV=y CONFIG_PWDX=y +CONFIG_PWGEN=y +CONFIG_READELF=y CONFIG_READLINK=y CONFIG_REALPATH=y CONFIG_RESET=y CONFIG_REV=y CONFIG_SETSID=y +CONFIG_SHA3SUM=y CONFIG_SHRED=y +CONFIG_STAT=y CONFIG_SYSCTL=y CONFIG_TAC=y CONFIG_TIMEOUT=y @@ -106,13 +117,29 @@ CONFIG_USLEEP=y CONFIG_UUIDGEN=y CONFIG_VMSTAT=y -CONFIG_WATCH=y CONFIG_W=y +CONFIG_WATCH=y CONFIG_WHICH=y CONFIG_XXD=y CONFIG_YES=y +CONFIG_FTPGET=y +CONFIG_FTPPUT=y +CONFIG_HOST=y +CONFIG_HTTPD=y +CONFIG_MICROCOM=y +CONFIG_NETCAT=y +CONFIG_WGET=y +CONFIG_GUNZIP=y +CONFIG_ZCAT=y CONFIG_HOSTNAME=y +CONFIG_DNSDOMAINNAME=y CONFIG_KILLALL=y +CONFIG_MD5SUM=y +CONFIG_SHA1SUM=y +CONFIG_SHA224SUM=y +CONFIG_SHA256SUM=y +CONFIG_SHA384SUM=y +CONFIG_SHA512SUM=y CONFIG_MKNOD=y CONFIG_MKTEMP=y CONFIG_PIDOF=y diff -Nru toybox-0.8.8+dfsg/kconfig/lkc.h toybox-0.8.9+dfsg/kconfig/lkc.h --- toybox-0.8.8+dfsg/kconfig/lkc.h 2022-08-12 07:58:03.000000000 +0000 +++ toybox-0.8.9+dfsg/kconfig/lkc.h 2023-01-10 19:24:45.000000000 +0000 @@ -152,4 +152,8 @@ } #endif +#ifndef PATH_MAX +#define PATH_MAX 4096 +#endif + #endif /* LKC_H */ diff -Nru toybox-0.8.8+dfsg/lib/args.c toybox-0.8.9+dfsg/lib/args.c --- toybox-0.8.8+dfsg/lib/args.c 2022-08-12 07:58:03.000000000 +0000 +++ toybox-0.8.9+dfsg/lib/args.c 2023-01-10 19:24:45.000000000 +0000 @@ -74,7 +74,7 @@ // <0 die if less than # leftover arguments (default 0) // >9 die if > # leftover arguments (default MAX_INT) // 0 Include argv[0] in optargs -// ^ stop at first nonoption argument (implied when no flags) +// ^ stop at first nonoption argument // ? Pass unknown arguments through to command (implied when no flags). // & first arg has imaginary dash (ala tar/ps/ar) which sets FLAGS_NODASH // ~ Collate following bare longopts (as if under short opt, repeatable) @@ -252,7 +252,7 @@ // Parse option string into a linked list of options with attributes. - if (!*options) gof->stopearly++, gof->noerror++; + if (!*options) gof->noerror++; while (*options) { char *temp; diff -Nru toybox-0.8.8+dfsg/lib/deflate.c toybox-0.8.9+dfsg/lib/deflate.c --- toybox-0.8.8+dfsg/lib/deflate.c 2022-08-12 07:58:03.000000000 +0000 +++ toybox-0.8.9+dfsg/lib/deflate.c 2023-01-10 19:24:45.000000000 +0000 @@ -17,7 +17,7 @@ void *fixdisthuff, *fixlithuff; // CRC - void (*crcfunc)(struct deflate *dd, char *data, int len); + void (*crcfunc)(struct deflate *dd, char *data, unsigned len); unsigned crctable[256], crc; @@ -49,16 +49,21 @@ // Advance bitpos without the overhead of recording bits // Loads more data when input buffer empty -static void bitbuf_skip(struct bitbuf *bb, int bits) +// call with 0 to just load data, returns 0 at EOF +static int bitbuf_skip(struct bitbuf *bb, int bits) { - int pos = bb->bitpos + bits, len = bb->len << 3; + int pos = bb->bitpos + bits + (bits<0), len; - while (pos >= len) { + while (pos >= (len = bb->len<<3)) { pos -= len; - len = (bb->len = read(bb->fd, bb->buf, bb->max)) << 3; - if (bb->len < 1) perror_exit("inflate EOF"); + if (1 > (bb->len = read(bb->fd, bb->buf, bb->max))) { + if (!bb->len && !bits) break; + error_exit("inflate EOF"); + } } bb->bitpos = pos; + + return posbitpos>>3; if (bufpos == bb->len) { - bitbuf_skip(bb, 0); + bitbuf_skip(bb, -1); bufpos = 0; } @@ -83,7 +88,10 @@ int click = bb->bitpos >> 3, blow, blen; // Load more data if buffer empty - if (click == bb->len) bitbuf_skip(bb, click = 0); + if (click == bb->len) { + bitbuf_skip(bb, -1); + click = 0; + } // grab bits from next byte blow = bb->bitpos & 7; @@ -194,6 +202,7 @@ static void inflate(struct deflate *dd, struct bitbuf *bb) { dd->crc = ~0; + // repeat until spanked for (;;) { int final, type; @@ -411,17 +420,32 @@ return 1; } -static void gzip_crc(struct deflate *dd, char *data, int len) +static void gzip_crc(struct deflate *dd, char *data, unsigned len) { int i; unsigned crc, *crc_table = dd->crctable; crc = dd->crc; - for (i=0; i>8); + for (i = 0; i>8); dd->crc = crc; dd->len += len; } +/* +// Start with crc = 1, or pass in last crc to append more data +unsigned adler32(char *buf, unsigned len, unsigned crc) +{ + unsigned aa = crc&((1<<16)-1), bb = crc>>16; + + while (len--) { + aa = (aa+*buf)%65521; + bb = (bb+aa)%65521; + } + + return (bb<16)+aa; +} +*/ + long long gzip_fd(int infd, int outfd) { struct bitbuf *bb = bitbuf_init(outfd, 4096); @@ -460,24 +484,27 @@ { struct bitbuf *bb = bitbuf_init(infd, 4096); struct deflate *dd = init_deflate(0); - long long rc; - - if (!is_gzip(bb)) error_exit("not gzip"); - dd->outfd = outfd; + long long rc = 0; // Little endian crc table crc_init(dd->crctable, 1); dd->crcfunc = gzip_crc; + dd->outfd = outfd; - inflate(dd, bb); - - // tail: crc32, len32 + do { + if (!is_gzip(bb)) error_exit("not gzip"); - bitbuf_skip(bb, (8-bb->bitpos)&7); - if (~dd->crc != bitbuf_get(bb, 32) || dd->len != bitbuf_get(bb, 32)) - error_exit("bad crc"); + inflate(dd, bb); - rc = dd->len; + // tail: crc32, len32 + bitbuf_skip(bb, (8-bb->bitpos)&7); + if (~dd->crc != bitbuf_get(bb, 32) || dd->len != bitbuf_get(bb, 32)) + error_exit("bad crc"); + rc += dd->len; + + bitbuf_skip(bb, (8-bb->bitpos)&7); + dd->pos = dd->len = 0; + } while (bitbuf_skip(bb, 0)); free(bb); free(dd); diff -Nru toybox-0.8.8+dfsg/lib/env.c toybox-0.8.9+dfsg/lib/env.c --- toybox-0.8.8+dfsg/lib/env.c 2022-08-12 07:58:03.000000000 +0000 +++ toybox-0.8.9+dfsg/lib/env.c 2023-01-10 19:24:45.000000000 +0000 @@ -61,7 +61,7 @@ for (i = 0; environ[i]; i++) { // Drop old entry, freeing as appropriate. Assumes no duplicates. - if (!memcmp(name, environ[i], len) && environ[i][len]=='=') { + if (!smemcmp(name, environ[i], len) && environ[i][len]=='=') { if (iname); if (msg || err) putc('\n', stderr); - if (!toys.exitval) toys.exitval++; + if (!toys.exitval) toys.exitval = (toys.which->flags>>24) ? : 1; } // These functions don't collapse together because of the va_stuff. @@ -738,7 +738,7 @@ // Inability to open a file prints a warning, but doesn't exit. if (!strcmp(*argv, "-")) fd = 0; - else if (0>(fd = notstdio(open(*argv, flags, permissions))) && !failok) { + else if (0>(fd = xnotstdio(open(*argv, flags, permissions))) && !failok) { perror_msg_raw(*argv); if (!anyway) continue; } @@ -1077,30 +1077,47 @@ return rc ? s2 : 0; } -// return (malloced) relative path to get from "from" to "to" -char *relative_path(char *from, char *to) +void *mepcpy(void *to, void *from, unsigned long len) +{ + memcpy(to, from, len); + + return ((char *)to)+len; +} + +// return (malloced) relative path to get between two normalized absolute paths +// normalized: no duplicate / or trailing / or .. or . (symlinks optional) +char *relative_path(char *from, char *to, int abs) { char *s, *ret = 0; int i, j, k; - if (!(from = xabspath(from, 0))) return 0; - if (!(to = xabspath(to, 0))) goto error; + if (abs) { + if (!(from = xabspath(from, 0))) return 0; + if (!(to = xabspath(to, 0))) goto error; + } - // skip common directories from root - for (i = j = 0; from[i] && from[i] == to[i]; i++) if (to[i] == '/') j = i+1; + for (i = j = 0;; i++) { + if (!from[i] || !to[i]) { + if (from[i]=='/' || to[i]=='/' || from[i]==to[i]) j = i; + break; + } + if (from[i] != to[i]) break; + if (from[i] == '/') j = i; + } // count remaining destination directories for (i = j, k = 0; from[i]; i++) if (from[i] == '/') k++; - - if (!k) ret = xstrdup(to+j); + if (!k) ret = xstrdup(to[j] ? to+j : "."); else { - s = ret = xmprintf("%*c%s", 3*k, ' ', to+j); - while (k--) memcpy(s+3*k, "../", 3); + s = ret = xmprintf("%*c%s", 3*k-!!k, ' ', to+j); + for (i = 0; inum) return cache; - cache = cache->next; - } - - return 0; -} - -// Uniquely add num+data to cache. Updates *cache, returns pointer to existing -// entry if it was already there. -struct num_cache *add_num_cache(struct num_cache **cache, long long num, - void *data, int len) -{ - struct num_cache *old = get_num_cache(*cache, num); - - if (old) return old; - - old = xzalloc(sizeof(struct num_cache)+len); - old->next = *cache; - old->num = num; - memcpy(old->data, data, len); - *cache = old; - - return 0; -} diff -Nru toybox-0.8.8+dfsg/lib/net.c toybox-0.8.9+dfsg/lib/net.c --- toybox-0.8.8+dfsg/lib/net.c 2022-08-12 07:58:03.000000000 +0000 +++ toybox-0.8.9+dfsg/lib/net.c 2023-01-10 19:24:45.000000000 +0000 @@ -197,18 +197,25 @@ } // Convert %XX escapes to character (in place) -void unescape_url(char *str) +char *unescape_url(char *str, int do_cut) { - char *to; + char *to, *cut = do_cut ? strchr(str, '?') : 0; int i; for (to = str;;) { if (*str!='%' || !isxdigit(str[1]) || !isxdigit(str[2])) { - if (!(*to++ = *str++)) break; + if (str==cut) { + *to = 0; + cut++; + + break; + } else if (!(*to++ = *str++)) break; } else { sscanf(++str, "%2x", &i); *to++ = i; str += 2; } } + + return cut; } diff -Nru toybox-0.8.8+dfsg/lib/password.c toybox-0.8.9+dfsg/lib/password.c --- toybox-0.8.8+dfsg/lib/password.c 2022-08-12 07:58:03.000000000 +0000 +++ toybox-0.8.9+dfsg/lib/password.c 2023-01-10 19:24:45.000000000 +0000 @@ -23,7 +23,7 @@ if (al[i].id) s += sprintf(s, "$%c$", '0'+al[i].id); // Read appropriate number of random bytes for salt - xgetrandom(libbuf, ((len*6)+7)/8, 0); + xgetrandom(libbuf, ((len*6)+7)/8); // Grab 6 bit chunks and convert to characters in ./0-9a-zA-Z for (i = 0; i) - if (!getentropy(buf, buflen)) return 1; - if (errno!=ENOSYS && !(flags&WARN_ONLY)) perror_exit("getrandom"); + while (buflen) { + if (getentropy(buf, fd = buflen>256 ? 256 : buflen)) break; + buflen -= fd; + buf += fd; + } + if (!buflen) return; + if (errno!=ENOSYS) perror_exit("getrandom"); #endif - fd = xopen(flags ? "/dev/random" : "/dev/urandom",O_RDONLY|(flags&WARN_ONLY)); - if (fd == -1) return 0; - xreadall(fd, buf, buflen); + xreadall(fd = xopen("/dev/urandom", O_RDONLY), buf, buflen); close(fd); - - return 1; } // Get list of mounted filesystems, including stat and statvfs info. @@ -96,30 +97,6 @@ #include -static void octal_deslash(char *s) -{ - char *o = s; - - while (*s) { - if (*s == '\\') { - int i, oct = 0; - - for (i = 1; i < 4; i++) { - if (!isdigit(s[i])) break; - oct = (oct<<3)+s[i]-'0'; - } - if (i == 4) { - *o++ = oct; - s += i; - continue; - } - } - *o++ = *s++; - } - - *o = 0; -} - // Check if this type matches list. // Odd syntax: typelist all yes = if any, typelist all no = if none. @@ -586,7 +563,8 @@ {0x3434, "nilfs"}, {0x6969, "nfs"}, {0x9fa0, "proc"}, {0x534F434B, "sockfs"}, {0x62656572, "sysfs"}, {0x517B, "smb"}, {0x4d44, "msdos"}, {0x4006, "fat"}, {0x43415d53, "smackfs"}, - {0x73717368, "squashfs"} + {0x73717368, "squashfs"}, {0xF2F52010, "f2fs"}, {0xE0F5E1E2, "erofs"}, + {0x2011BAB0, "exfat"}, }; int i; @@ -625,6 +603,11 @@ *size = lab.d_secsize * lab.d_nsectors; return status; } +#else +int get_block_device_size(int fd, unsigned long long* size) +{ + return 0; +} #endif // Return bytes copied from in to out. If bytes <0 copy all of in to out. @@ -651,7 +634,7 @@ errno = EINVAL; len = -1; #endif - if (len < 0 && errno == EINVAL) { + if (len < 0) { try_cfr = 0; continue; @@ -711,6 +694,12 @@ return 0; } +#if !defined(SYS_timer_settime) && defined(SYS_timer_settime64) +// glibc does not define defines SYS_timer_settime on 32-bit systems +// with 64-bit time_t defaults e.g. riscv32 +#define SYS_timer_settime SYS_timer_settime64 +#endif + int timer_settime_wrap(timer_t t, int flags, struct itimerspec *val, struct itimerspec *old) { diff -Nru toybox-0.8.8+dfsg/lib/portability.h toybox-0.8.9+dfsg/lib/portability.h --- toybox-0.8.8+dfsg/lib/portability.h 2022-08-12 07:58:03.000000000 +0000 +++ toybox-0.8.9+dfsg/lib/portability.h 2023-01-10 19:24:45.000000000 +0000 @@ -114,9 +114,6 @@ const void *needle, size_t needle_length); #endif // defined(glibc) -// getopt_long(), getopt_long_only(), and struct option. -#include - #if !defined(__GLIBC__) // POSIX basename. #include @@ -148,6 +145,10 @@ #define IS_BIG_ENDIAN 0 #endif +#define bswap_16(x) bswap16(x) +#define bswap_32(x) bswap32(x) +#define bswap_64(x) bswap64(x) + #else #include @@ -255,6 +256,9 @@ // Some systems don't define O_NOFOLLOW, and it varies by architecture, so... #include +#if defined(__APPLE__) +#define O_PATH 0 +#else #ifndef O_NOFOLLOW #define O_NOFOLLOW 0 #endif @@ -270,6 +274,7 @@ #ifndef SCHED_RESET_ON_FORK #define SCHED_RESET_ON_FORK (1<<30) #endif +#endif // Glibc won't give you linux-kernel constants unless you say "no, a BUD lite" // even though linux has nothing to do with the FSF and never has. @@ -350,7 +355,7 @@ #if __has_include () #include #endif -int xgetrandom(void *buf, unsigned len, unsigned flags); +void xgetrandom(void *buf, unsigned len); // Android's bionic libc doesn't have confstr. #ifdef __BIONIC__ diff -Nru toybox-0.8.8+dfsg/lib/tty.c toybox-0.8.9+dfsg/lib/tty.c --- toybox-0.8.8+dfsg/lib/tty.c 2022-08-12 07:58:03.000000000 +0000 +++ toybox-0.8.9+dfsg/lib/tty.c 2023-01-10 19:24:45.000000000 +0000 @@ -22,7 +22,7 @@ for (i = 0; i<3; i++) if (isatty(j = (i+1)%3)) return j; - return notstdio(open("/dev/tty", O_RDWR)); + return xnotstdio(open("/dev/tty", O_RDWR)); } // Query size of terminal (without ANSI probe fallback). diff -Nru toybox-0.8.8+dfsg/lib/xwrap.c toybox-0.8.9+dfsg/lib/xwrap.c --- toybox-0.8.8+dfsg/lib/xwrap.c 2022-08-12 07:58:03.000000000 +0000 +++ toybox-0.8.9+dfsg/lib/xwrap.c 2023-01-10 19:24:45.000000000 +0000 @@ -339,7 +339,7 @@ // Wait for child process to exit, then return adjusted exit code. int xwaitpid(pid_t pid) { - int status; + int status = 127<<8; while (-1 == waitpid(pid, &status, 0) && errno == EINTR) errno = 0; @@ -382,15 +382,15 @@ return xpclose_both(xpopen_both(argv, 0), 0); } -// Run child, writing "stdin", returning stdout or NULL, pass through stderr -char *xrunread(char *argv[], char *stdin) +// Run child, writing to_stdin, returning stdout or NULL, pass through stderr +char *xrunread(char *argv[], char *to_stdin) { char *result = 0; int pipe[] = {-1, -1}, total = 0, len; pid_t pid; pid = xpopen_both(argv, pipe); - if (stdin && *stdin) writeall(*pipe, stdin, strlen(stdin)); + if (to_stdin && *to_stdin) writeall(*pipe, to_stdin, strlen(to_stdin)); close(*pipe); for (;;) { if (0>=(len = readall(pipe[1], libbuf, sizeof(libbuf)))) break; @@ -457,9 +457,7 @@ return fd; } -// Move file descriptor above stdin/stdout/stderr, using /dev/null to consume -// old one. (We should never be called with stdin/stdout/stderr closed, but...) -int notstdio(int fd) +int xnotstdio(int fd) { if (fd<0) return fd; @@ -492,13 +490,13 @@ // Create a file but don't return stdin/stdout/stderr int xcreate(char *path, int flags, int mode) { - return notstdio(xcreate_stdio(path, flags, mode)); + return xnotstdio(xcreate_stdio(path, flags, mode)); } // Open a file descriptor NOT in stdin/stdout/stderr int xopen(char *path, int flags) { - return notstdio(xopen_stdio(path, flags)); + return xnotstdio(xopen_stdio(path, flags)); } // Open read only, treating "-" as a synonym for stdin, defaulting to warn only @@ -631,8 +629,8 @@ } // Is this a symlink? - if (flags & (ABS_KEEP<str, libbuf, sizeof(libbuf)); + if (flags & (ABS_KEEP<4095) goto error; // Not a symlink: add to linked list, move dirfd, fail if error diff -Nru toybox-0.8.8+dfsg/main.c toybox-0.8.9+dfsg/main.c --- toybox-0.8.8+dfsg/main.c 2022-08-12 07:58:03.000000000 +0000 +++ toybox-0.8.9+dfsg/main.c 2023-01-10 19:24:45.000000000 +0000 @@ -93,6 +93,11 @@ // If it's an alias, restart search for real name if (*s != 255) break; i = toy_find(++s)-toy_list; + if ((full&4) && toy_list[i].flags) { + fprintf(out, "See %s\n", s, s); + + return; + } } if (full) fprintf(out, "%s\n", s); diff -Nru toybox-0.8.8+dfsg/Makefile toybox-0.8.9+dfsg/Makefile --- toybox-0.8.8+dfsg/Makefile 2022-08-12 07:58:03.000000000 +0000 +++ toybox-0.8.9+dfsg/Makefile 2023-01-10 19:24:45.000000000 +0000 @@ -73,7 +73,7 @@ @rm -f toybox* .config* .singlemake @echo removed .config -tests: +tests: toybox scripts/test.sh root: diff -Nru toybox-0.8.8+dfsg/scripts/git-static-index.sh toybox-0.8.9+dfsg/scripts/git-static-index.sh --- toybox-0.8.8+dfsg/scripts/git-static-index.sh 1970-01-01 00:00:00.000000000 +0000 +++ toybox-0.8.9+dfsg/scripts/git-static-index.sh 2023-01-10 19:24:45.000000000 +0000 @@ -0,0 +1,28 @@ +#!/bin/bash + +# Create very basic index.html and commit links for a static git archive + +mkdir -p commit +git log --pretty=%H | while read i +do + [ -e commit/$i ] && break + git format-patch -1 --stdout $i > commit/$i + ln -sf $i commit/${i::12} +done + +echo '' +echo '' +git log --pretty='%H%n%an<%ae>%n%ad%n%s' --date=format:'%r
%d-%m-%Y' | while read HASH +do + HASH="${HASH::12}" + read AUTHOR + AUTHOR1="${AUTHOR/<*/}" + AUTHOR1="${AUTHOR1::17}" + AUTHOR2="<${AUTHOR/*" +done +echo "
commitauthordatedescription
$HASH$AUTHOR1
$AUTHOR2
$DATE$DESC
" diff -Nru toybox-0.8.8+dfsg/scripts/install.sh toybox-0.8.9+dfsg/scripts/install.sh --- toybox-0.8.8+dfsg/scripts/install.sh 2022-08-12 07:58:03.000000000 +0000 +++ toybox-0.8.9+dfsg/scripts/install.sh 2023-01-10 19:24:45.000000000 +0000 @@ -31,7 +31,6 @@ echo "Compile instlist..." -NOBUILD=1 scripts/make.sh $DEBUG $HOSTCC -I . scripts/install.c -o "$UNSTRIPPED"/instlist || exit 1 COMMANDS="$("$UNSTRIPPED"/instlist $LONG_PATH)" @@ -106,11 +105,9 @@ # The following are commands toybox should provide, but doesn't yet. # For now symlink the host version. This list must go away by 1.0. -PENDING="dd diff expr git tr vi bash sh xzcat bc ar gzip less awk unxz bison flex make nm" - -# "gcc" can go away if the kernel guys merge my patch: -# http://lkml.iu.edu/hypermail/linux/kernel/2202.0/01505.html -TOOLCHAIN="as cc ld gcc objdump" +PENDING="expr git tr bash sh gzip awk bison flex make" +TOOLCHAIN="as cc ld objdump" +TOOLCHAIN+="bc gcc" # both patched out but not in vanilla yet # Tools needed to build packages for i in $TOOLCHAIN $PENDING $HOST_EXTRA diff -Nru toybox-0.8.8+dfsg/scripts/make.sh toybox-0.8.9+dfsg/scripts/make.sh --- toybox-0.8.8+dfsg/scripts/make.sh 2022-08-12 07:58:03.000000000 +0000 +++ toybox-0.8.9+dfsg/scripts/make.sh 2023-01-10 19:24:45.000000000 +0000 @@ -85,7 +85,7 @@ [ -z "$V" ] && X=/dev/null || X=/dev/stderr for i in util crypt m resolv selinux smack attr crypto z log iconv tls ssl do - do_loudly ${CROSS_COMPILE}${CC} $CFLAGS $LDFLAGS -xc - -l$i &>$X \ + do_loudly ${CROSS_COMPILE}${CC} $CFLAGS $LDFLAGS -xc - -l$i >>$X 2>&1 \ -o "$UNSTRIPPED"/libprobe <<<"int main(int argc,char*argv[]){return 0;}"&& do_loudly echo -n ' '-l$i >> "$GENDIR"/optlibs.new done @@ -243,8 +243,7 @@ [ -n "$NOBUILD" ] && exit 0 -echo -n "Compile $OUTNAME" -[ -n "$V" ] && echo +echo "Compile $OUTNAME" DOTPROG=. # This is a parallel version of: do_loudly $BUILD lib/*.c $TOYFILES $LINK @@ -304,4 +303,4 @@ # multiplexer binary via truncate-and-write through a symlink. do_loudly chmod 555 "$OUTNAME" || exit 1 -echo +[ -z "$V" ] && echo >&2 diff -Nru toybox-0.8.8+dfsg/scripts/mkflags.c toybox-0.8.9+dfsg/scripts/mkflags.c --- toybox-0.8.8+dfsg/scripts/mkflags.c 2022-08-12 07:58:03.000000000 +0000 +++ toybox-0.8.9+dfsg/scripts/mkflags.c 2023-01-10 19:24:45.000000000 +0000 @@ -1,4 +1,4 @@ -// Take three word input lines on stdin and produce flag #defines to stdout. +// Read three word input lines from stdin and produce flag #defines to stdout. // The three words on each input line are command name, option string with // current config, option string from allyesconfig. The three are space // separated and the last two are in double quotes. @@ -170,9 +170,8 @@ // See "intentionally crappy", above. if (!(out = outbuf)) return 1; - printf("#undef FORCED_FLAG\n#undef FORCED_FLAGLL\n" - "#ifdef FORCE_FLAGS\n#define FORCED_FLAG 1\n#define FORCED_FLAGLL 1ULL\n" - "#else\n#define FORCED_FLAG 0\n#define FORCED_FLAGLL 0LL\n#endif\n\n"); + printf("#undef FORCED_FLAG\n#ifdef FORCE_FLAGS\n#define FORCED_FLAG 1LL\n" + "#else\n#define FORCED_FLAG 0LL\n#endif\n\n"); for (;;) { struct flag *flist, *aflist, *offlist; @@ -230,7 +229,7 @@ out += strlen(out); while (aflist) { - char *llstr = bit>30 ? "LL" : "", *s = (char []){0, 0, 0, 0}; + char *s = (char []){0, 0, 0, 0}; int enabled = 0; // Output flag macro for bare longopts @@ -245,8 +244,8 @@ if (flist && flist->command && *aflist->command == *flist->command) enabled++; } - out += sprintf(out, "#define FLAG_%s (%s%s<<%d)\n", - s, enabled ? "1" : "FORCED_FLAG", llstr, bit++); + out += sprintf(out, "#define FLAG_%s (%s<<%d)\n", + s, enabled ? "1LL" : "FORCED_FLAG", bit++); aflist = aflist->next; if (enabled) flist = flist->next; } diff -Nru toybox-0.8.8+dfsg/scripts/mkroot.sh toybox-0.8.9+dfsg/scripts/mkroot.sh --- toybox-0.8.8+dfsg/scripts/mkroot.sh 2022-08-12 07:58:03.000000000 +0000 +++ toybox-0.8.9+dfsg/scripts/mkroot.sh 2023-01-10 19:24:45.000000000 +0000 @@ -12,8 +12,8 @@ done # Set default directory locations (overrideable from command line) -: ${LOG:=${BUILD:=${TOP:=$PWD/root}/build}/log} ${AIRLOCK:=$BUILD/airlock} -: ${CCC:=$PWD/ccc} ${PKGDIR:=$PWD/scripts/root} +: ${TOP:=$PWD/root} ${BUILD:=$TOP/build} ${LOG:=$BUILD/log} +: ${AIRLOCK:=$BUILD/airlock} ${CCC:=$PWD/ccc} ${PKGDIR:=$PWD/scripts/root} # define functions announce() { printf "\033]2;$CROSS $*\007" >/dev/tty; printf "\n=== $*\n";} @@ -63,6 +63,7 @@ rm .singleconfig_airlock || exit 1 fi export PATH="$AIRLOCK" + CPIO_OPTS+=--no-preserve-owner fi # Create per-target work directories @@ -116,6 +117,7 @@ echo 0 99999 > /proc/sys/net/ipv4/ping_group_range if [ $$ -eq 1 ]; then # Setup networking for QEMU (needs /proc) + mountpoint -q mnt || [ -e /dev/?da ] && mount /dev/?da /mnt ifconfig lo 127.0.0.1 ifconfig eth0 10.0.2.15 route add default gw 10.0.2.2 @@ -126,6 +128,7 @@ for i in $(ls -1 /etc/rc 2>/dev/null | sort); do . /etc/rc/"$i"; done [ -z "$CONSOLE" ] && CONSOLE="$(
/proc/sys/kernel/printk exec oneit -c /dev/"${CONSOLE:-console}" $HANDOFF @@ -154,7 +157,7 @@ # Build static toybox with existing .config if there is one, else defconfig+sh announce toybox -[ ! -z "$PENDING" ] && rm -f .config +[ -n "$PENDING" ] && rm -f .config [ -e .config ] && CONF=silentoldconfig || unset CONF for i in $PENDING sh route; do XX="$XX"$'\n'CONFIG_${i^^?}=y; done [ -e "$ROOT"/lib/libc.so ] || export LDFLAGS=--static @@ -164,6 +167,9 @@ # ------------------ Part 3: Build + package bootable system ------------------ +# Convert comma separated values in $1 to CONFIG=$2 lines +csv2cfg() { sed -E '/^$/d;s/([^,]*)($|,)/CONFIG_\1='"$2"'\n/g' <<< "$1"; } + # ----- Build kernel for target if [ -z "$LINUX" ] || [ ! -d "$LINUX/kernel" ]; then @@ -211,7 +217,7 @@ KCONF=$KCONF,UNWINDER_FRAME_POINTER,PCI,BLK_DEV_SD,ATA,ATA_SFF,ATA_BMDMA,ATA_PIIX,NET_VENDOR_INTEL,E1000,SERIAL_8250,SERIAL_8250_CONSOLE,RTC_CLASS elif [ "$TARGET" == m68k ]; then QEMU="m68k -M q800" KARCH=m68k KARGS=ttyS0 VMLINUX=vmlinux - KCONF=MMU,M68040,M68KFPU_EMU,MAC,SCSI_MAC_ESP,MACINTOSH_DRIVERS,ADB,ADB_MACII,NET_CORE,NET_VENDOR_NATSEMI,MACSONIC,SERIAL_PMACZILOG,SERIAL_PMACZILOG_TTYS,SERIAL_PMACZILOG_CONSOLE + KCONF=MMU,M68040,M68KFPU_EMU,MAC,SCSI,SCSI_LOWLEVEL,BLK_DEV_SD,SCSI_MAC_ESP,MACINTOSH_DRIVERS,NET_CORE,NET_VENDOR_NATSEMI,MACSONIC,SERIAL_PMACZILOG,SERIAL_PMACZILOG_TTYS,SERIAL_PMACZILOG_CONSOLE elif [ "$TARGET" == mips ] || [ "$TARGET" == mipsel ]; then QEMU="mips -M malta" KARCH=mips KARGS=ttyS0 VMLINUX=vmlinux KCONF=MIPS_MALTA,CPU_MIPS32_R2,SERIAL_8250,SERIAL_8250_CONSOLE,PCI,BLK_DEV_SD,ATA,ATA_SFF,ATA_BMDMA,ATA_PIIX,NET_VENDOR_AMD,PCNET32,POWER_RESET,POWER_RESET_SYSCON @@ -219,11 +225,12 @@ QEMU="mipsel -M malta" elif [ "$TARGET" == powerpc ]; then KARCH=powerpc QEMU="ppc -M g3beige" KARGS=ttyS0 VMLINUX=vmlinux - KCONF=ALTIVEC,PPC_PMAC,PPC_OF_BOOT_TRAMPOLINE,IDE,IDE_GD,IDE_GD_ATA,BLK_DEV_IDE_PMAC,BLK_DEV_IDE_PMAC_ATA100FIRST,MACINTOSH_DRIVERS,ADB,ADB_CUDA,NET_VENDOR_NATSEMI,NET_VENDOR_8390,NE2K_PCI,SERIO,SERIAL_PMACZILOG,SERIAL_PMACZILOG_TTYS,SERIAL_PMACZILOG_CONSOLE,BOOTX_TEXT - elif [ "$TARGET" == powerpc64le ]; then + KCONF=ALTIVEC,PPC_PMAC,PPC_OF_BOOT_TRAMPOLINE,ATA,ATA_SFF,ATA_BMDMA,PATA_MACIO,BLK_DEV_SD,MACINTOSH_DRIVERS,ADB,ADB_CUDA,NET_VENDOR_NATSEMI,NET_VENDOR_8390,NE2K_PCI,SERIO,SERIAL_PMACZILOG,SERIAL_PMACZILOG_TTYS,SERIAL_PMACZILOG_CONSOLE,BOOTX_TEXT + elif [ "$TARGET" == powerpc64 ] || [ "$TARGET" == powerpc64le ]; then KARCH=powerpc QEMU="ppc64 -M pseries -vga none" KARGS=hvc0 VMLINUX=vmlinux - KCONF=PPC64,PPC_PSERIES,CPU_LITTLE_ENDIAN,PPC_OF_BOOT_TRAMPOLINE,BLK_DEV_SD,SCSI_LOWLEVEL,SCSI_IBMVSCSI,ATA,NET_VENDOR_IBM,IBMVETH,HVC_CONSOLE,PPC_TRANSACTIONAL_MEM,PPC_DISABLE_WERROR,SECTION_MISMATCH_WARN_ONLY + KCONF=PPC64,PPC_PSERIES,PPC_OF_BOOT_TRAMPOLINE,BLK_DEV_SD,SCSI_LOWLEVEL,SCSI_IBMVSCSI,ATA,NET_VENDOR_IBM,IBMVETH,HVC_CONSOLE,PPC_TRANSACTIONAL_MEM,PPC_DISABLE_WERROR,SECTION_MISMATCH_WARN_ONLY + [ "$TARGET" == powerpc64le ] && KCONF=$KCONF,CPU_LITTLE_ENDIAN elif [ "$TARGET" = s390x ]; then QEMU="s390x" KARCH=s390 VMLINUX=arch/s390/boot/bzImage KCONF=MARCH_Z900,PACK_STACK,NET_CORE,VIRTIO_NET,VIRTIO_BLK,SCLP_TTY,SCLP_CONSOLE,SCLP_VT220_TTY,SCLP_VT220_CONSOLE,S390_GUEST @@ -254,22 +261,19 @@ announce "linux-$KARCH" pushd "$LINUX" && make distclean && popd && cp -sfR "$LINUX" "$TEMP/linux" && pushd "$TEMP/linux" && - # Fix x86-64 and sh2eb - sed -Eis '/select HAVE_(STACK_VALIDATION|OBJTOOL)/d' arch/x86/Kconfig && - sed -is 's/depends on !SMP/& || !MMU/' mm/Kconfig && # Write linux-miniconfig { echo "# make ARCH=$KARCH allnoconfig KCONFIG_ALLCONFIG=linux-miniconfig" echo -e "# make ARCH=$KARCH -j \$(nproc)\n# boot $VMLINUX\n\n" - echo "# CONFIG_EMBEDDED is not set" # Expand list of =y symbols, first generic then architecture-specific for i in BINFMT_ELF,BINFMT_SCRIPT,NO_HZ,HIGH_RES_TIMERS,BLK_DEV,BLK_DEV_INITRD,RD_GZIP,BLK_DEV_LOOP,EXT4_FS,EXT4_USE_FOR_EXT2,VFAT_FS,FAT_DEFAULT_UTF8,MISC_FILESYSTEMS,SQUASHFS,SQUASHFS_XATTR,SQUASHFS_ZLIB,DEVTMPFS,DEVTMPFS_MOUNT,TMPFS,TMPFS_POSIX_ACL,NET,PACKET,UNIX,INET,IPV6,NETDEVICES,NET_CORE,NETCONSOLE,ETHERNET,COMPAT_32BIT_TIME,EARLY_PRINTK,IKCONFIG,IKCONFIG_PROC $KCONF $KEXTRA ; do echo "# architecture ${X:-independent}" - sed -E '/^$/d;s/([^,]*)($|,)/CONFIG_\1=y\n/g' <<< "$i" + csv2cfg "$i" y X=specific done [ -n "$BUILTIN" ] && echo -e CONFIG_INITRAMFS_SOURCE="\"$OUTPUT/fs\"" + for i in $MODULES; do csv2cfg "$i" m; done echo "$KERNEL_CONFIG" } > "$OUTPUT/linux-miniconfig" && make ARCH=$KARCH allnoconfig KCONFIG_ALLCONFIG="$OUTPUT/linux-miniconfig" && @@ -283,16 +287,22 @@ cp .config "$OUTPUT/linux-fullconfig" && # Build kernel. Copy config, device tree binary, and kernel binary to output - make ARCH=$KARCH CROSS_COMPILE="$CROSS_COMPILE" -j $(nproc) || exit 1 + make ARCH=$KARCH CROSS_COMPILE="$CROSS_COMPILE" -j $(nproc) all || exit 1 [ -n "$DTB" ] && { cp "$DTB" "$OUTPUT/linux.dtb" || exit 1 ;} + if [ -n "$MODULES" ]; then + make ARCH=$KARCH INSTALL_MOD_PATH=modz modules_install && + (cd modz && find lib/modules | cpio -o -H newc $CPIO_OPTS ) | gzip \ + > "$OUTPUT/modules.cpio.gz" || exit 1 + fi cp "$VMLINUX" "$OUTPUT"/linux-kernel && cd .. && rm -rf linux && popd ||exit 1 fi # clean up and package root filesystem for initramfs. if [ -z "$BUILTIN" ]; then announce initramfs - (cd "$ROOT" && find . | cpio -o -H newc ${CROSS_COMPILE:+--no-preserve-owner}\ - | gzip) > "$OUTPUT"/initramfs.cpio.gz || exit 1 + { (cd "$ROOT" && find . | cpio -o -H newc $CPIO_OPTS i ) || exit 1 + ! test -e "$OUTPUT/modules.cpio.gz" || zcat $_;} | gzip \ + > "$OUTPUT"/initramfs.cpio.gz || exit 1 fi mv "$LOG/$CROSS".{n,y} diff -Nru toybox-0.8.8+dfsg/scripts/mkstatus.py toybox-0.8.9+dfsg/scripts/mkstatus.py --- toybox-0.8.8+dfsg/scripts/mkstatus.py 2022-08-12 07:58:03.000000000 +0000 +++ toybox-0.8.9+dfsg/scripts/mkstatus.py 2023-01-10 19:24:45.000000000 +0000 @@ -40,7 +40,7 @@ # Run a couple sanity checks on input for i in toystuff: - if (i in pending): print "barf %s" % i + if (i in pending): print "Pending command not roadmap: %s" % i unknowns=[] for i in toystuff + pending: diff -Nru toybox-0.8.8+dfsg/scripts/portability.sh toybox-0.8.9+dfsg/scripts/portability.sh --- toybox-0.8.8+dfsg/scripts/portability.sh 2022-08-12 07:58:03.000000000 +0000 +++ toybox-0.8.9+dfsg/scripts/portability.sh 2023-01-10 19:24:45.000000000 +0000 @@ -2,9 +2,9 @@ source ./configure -if [ -z "$(command -v "${CROSS_COMPILE}${CC}")" ] +if [ -z "$(command -v "$CROSS_COMPILE$CC")" ] then - echo "No ${CROSS_COMPILE}${CC} found" >&2 + echo "No $CROSS_COMPILE$CC found" >&2 exit 1 fi @@ -22,16 +22,19 @@ : ${LDOPTIMIZE:=-Wl,--gc-sections -Wl,--as-needed} ${STRIP:=strip -s -R .note* -R .comment} fi +# Disable a pointless warning only clang produces +[ -n "$("$CROSS_COMPILE$CC" --version | grep -w clang)" ] && + CFLAGS+=" -Wno-string-plus-int" + # Address Sanitizer -if [ ! -z "$ASAN" ]; then +if [ -n "$ASAN" ]; then # Turn ASan on and disable most optimization to get more readable backtraces. # (Technically ASAN is just "-fsanitize=address" and the rest is optional.) - ASAN_FLAGS="-fsanitize=address -O1 -g -fno-omit-frame-pointer -fno-optimize-sibling-calls" - CFLAGS="$CFLAGS $ASAN_FLAGS" - HOSTCC="$HOSTCC $ASAN_FLAGS" - NOSTRIP=1 + export CFLAGS="$CFLAGS -fsanitize=address -O1 -g -fno-omit-frame-pointer -fno-optimize-sibling-calls" + export NOSTRIP=1 # Ignore leaks on exit. TODO export ASAN_OPTIONS="detect_leaks=0" + # only do this once unset ASAN fi diff -Nru toybox-0.8.8+dfsg/scripts/root/overlay toybox-0.8.9+dfsg/scripts/root/overlay --- toybox-0.8.8+dfsg/scripts/root/overlay 1970-01-01 00:00:00.000000000 +0000 +++ toybox-0.8.9+dfsg/scripts/root/overlay 2023-01-10 19:24:45.000000000 +0000 @@ -0,0 +1,3 @@ +#!/bin/echo Try "scripts/mkroot.sh overlay" + +cp -a "${OVERLAY:=overlay}"/. "$ROOT"/. diff -Nru toybox-0.8.8+dfsg/scripts/root/tests toybox-0.8.9+dfsg/scripts/root/tests --- toybox-0.8.8+dfsg/scripts/root/tests 1970-01-01 00:00:00.000000000 +0000 +++ toybox-0.8.9+dfsg/scripts/root/tests 2023-01-10 19:24:45.000000000 +0000 @@ -0,0 +1,15 @@ +#!/bin/echo Try "scripts/mkroot.sh $0" + +# Alas http://www.linux-usb.org/usb.ids is not versioned, so... +download 36d4e16755502fbc684be75e56841e1014e4a94a \ + https://github.com/usbids/usbids/raw/a5edeafb6099/usb.ids + +# Nor is https://pci-ids.ucw.cz/v2.2/pci.ids (tool version, not file version) +download 6694284723e034f0c564e81a30879939d5ef8b7e \ + https://github.com/pciutils/pciids/raw/c7929c0f9480/pci.ids + +cp "$DOWNLOAD"/{usb,pci}.ids "$ROOT/etc/" || exit 1 + +# Enable module support in the kernel and add a couple test modules +KEXTRA=MODULES,MODULE_UNLOAD,"$KEXTRA" +MODULES+=FSCACHE,CACHEFILES diff -Nru toybox-0.8.8+dfsg/scripts/runtest.sh toybox-0.8.9+dfsg/scripts/runtest.sh --- toybox-0.8.8+dfsg/scripts/runtest.sh 2022-08-12 07:58:03.000000000 +0000 +++ toybox-0.8.9+dfsg/scripts/runtest.sh 2023-01-10 19:24:45.000000000 +0000 @@ -2,9 +2,7 @@ # # Copyright 2005 by Rob Landley -# This file defines two main functions, "testcmd" and "optional". The -# first performs a test, the second enables/disables tests based on -# configuration options. +# This file defines three main functions: "testing", "testcmd", and "txpect". # The following environment variables enable optional behavior in "testing": # DEBUG - Show every command run by test script. @@ -30,26 +28,13 @@ # The environment variable "FAILCOUNT" contains a cumulative total of the # number of failed tests. # -# The "optional" function is used to skip certain tests (by setting the -# environment variable SKIP), ala: -# optional CFG_THINGY +# The environment variable "SKIP" says how many upcoming tests to skip, +# defaulting to 0 and counting down when set to a higher number. # -# The "optional" function checks the environment variable "OPTIONFLAGS", -# which is either empty (in which case it always clears SKIP) or -# else contains a colon-separated list of features (in which case the function -# clears SKIP if the flag was found, or sets it to 1 if the flag was not found). - -export FAILCOUNT=0 -export SKIP= - -# Helper functions - -# Check config to see if option is enabled, set SKIP if not. - -SHOWPASS=PASS -SHOWFAIL=FAIL -SHOWSKIP=SKIP +# Function "optional" enables/disables tests based on configuration options. +export FAILCOUNT=0 SKIP=0 +: ${SHOWPASS:=PASS} ${SHOWFAIL:=FAIL} ${SHOWSKIP:=SKIP} if tty -s <&1 then SHOWPASS="$(echo -e "\033[1;32m${SHOWPASS}\033[0m")" @@ -57,48 +42,14 @@ SHOWSKIP="$(echo -e "\033[1;33m${SHOWSKIP}\033[0m")" fi -optional() -{ - option=`printf %s "$OPTIONFLAGS" | egrep "(^|:)$1(:|\$)"` - # Not set? - if [ -z "$1" ] || [ -z "$OPTIONFLAGS" ] || [ ${#option} -ne 0 ] - then - unset SKIP - return - fi - SKIP=1 -} +# Helper functions +# Check if VERBOSE= contains a given string. (This allows combining.) verbose_has() { [ "${VERBOSE/$1/}" != "$VERBOSE" ] } -skipnot() -{ - if verbose_has quiet - then - eval "$@" 2>/dev/null - else - eval "$@" - fi - [ $? -eq 0 ] || SKIPNEXT=1 -} - -toyonly() -{ - IS_TOYBOX="$("$C" --version 2>/dev/null)" - # Ideally we'd just check for "toybox", but toybox sed lies to make autoconf - # happy, so we have at least two things to check for. - case "$IS_TOYBOX" in - toybox*) ;; - This\ is\ not\ GNU*) ;; - *) SKIPNEXT=1 ;; - esac - - "$@" -} - wrong_args() { if [ $# -ne 5 ] @@ -127,8 +78,43 @@ ! verbose_has all && exit 1 } -# The testing function +# Functions test files call directly + +# Set SKIP high if option not enabled in $OPTIONFLAGS (unless OPTIONFLAGS blank) +optional() +{ + [ -n "$OPTIONFLAGS" ] && [ "$OPTIONFLAGS" == "${OPTIONFLAGS/:$1:/}" ] && + SKIP=99999 || SKIP=0 +} +# Evalute command line and skip next test when false +skipnot() +{ + if verbose_has quiet + then + eval "$@" >/dev/null 2>&1 + else + eval "$@" + fi + [ $? -eq 0 ] || { ((++SKIP)); return 1; } +} + +# Skip this test (rest of command line) when not running toybox. +toyonly() +{ + IS_TOYBOX="$("$C" --version 2>/dev/null)" + # Ideally we'd just check for "toybox", but toybox sed lies to make autoconf + # happy, so we have at least two things to check for. + case "$IS_TOYBOX" in + toybox*) ;; + This\ is\ not\ GNU*) ;; + *) [ $SKIP -eq 0 ] && ((++SKIP)) ;; + esac + + "$@" +} + +# Takes five arguments: "name" "command" "result" "infile" "stdin" testing() { NAME="$CMDNAME $1" @@ -138,10 +124,11 @@ [ -n "$DEBUG" ] && set -x - if [ -n "$SKIP" -o -n "$SKIP_HOST" -a -n "$TEST_HOST" -o -n "$SKIPNEXT" ] + if [ "$SKIP" -gt 0 ] then - verbose_has quiet && printf "%s\n" "$SHOWSKIP: $NAME" - unset SKIPNEXT + verbose_has quiet || printf "%s\n" "$SHOWSKIP: $NAME" + ((--SKIP)) + return 0 fi @@ -170,6 +157,7 @@ return 0 } +# Wrapper for "testing", adds command name being tested to start of command line testcmd() { wrong_args "$@" @@ -179,13 +167,15 @@ testing "$X" "\"$C\" $2" "$3" "$4" "$5" } +# Simple implementation of "expect" written in shell. + # txpect NAME COMMAND [I/O/E/Xstring]... # Run COMMAND and interact with it: send I strings to input, read O or E # strings from stdout or stderr (empty string is "read line of input here"), # X means close stdin/stdout/stderr and match return code (blank means nonzero) txpect() { - local NAME CASE VERBOSITY LEN A B X O + local NAME CASE VERBOSITY LEN PID A B X O # Run command with redirection through fifos NAME="$CMDNAME $1" @@ -198,13 +188,14 @@ return fi eval "$2" out-$$ 2>err-$$ & + PID=$! shift 2 : {IN}>in-$$ {OUT}&2 + do_fail fi } - -# Recursively grab an executable and all the libraries needed to run it. -# Source paths beginning with / will be copied into destpath, otherwise -# the file is assumed to already be there and only its library dependencies -# are copied. - -mkchroot() -{ - [ $# -lt 2 ] && return - - echo -n . - - dest=$1 - shift - for i in "$@" - do - [ "${i:0:1}" == "/" ] || i=$(which $i) - [ -f "$dest/$i" ] && continue - if [ -e "$i" ] - then - d=`echo "$i" | grep -o '.*/'` && - mkdir -p "$dest/$d" && - cat "$i" > "$dest/$i" && - chmod +x "$dest/$i" - else - echo "Not found: $i" - fi - mkchroot "$dest" $(ldd "$i" | egrep -o '/.* ') - done -} - -# Set up a chroot environment and run commands within it. -# Needed commands listed on command line -# Script fed to stdin. - -dochroot() -{ - mkdir tmpdir4chroot - mount -t ramfs tmpdir4chroot tmpdir4chroot - mkdir -p tmpdir4chroot/{etc,sys,proc,tmp,dev} - cp -L testing.sh tmpdir4chroot - - # Copy utilities from command line arguments - - echo -n "Setup chroot" - mkchroot tmpdir4chroot $* - echo - - mknod tmpdir4chroot/dev/tty c 5 0 - mknod tmpdir4chroot/dev/null c 1 3 - mknod tmpdir4chroot/dev/zero c 1 5 - - # Copy script from stdin - - cat > tmpdir4chroot/test.sh - chmod +x tmpdir4chroot/test.sh - chroot tmpdir4chroot /test.sh - umount -l tmpdir4chroot - rmdir tmpdir4chroot -} diff -Nru toybox-0.8.8+dfsg/scripts/single.sh toybox-0.8.9+dfsg/scripts/single.sh --- toybox-0.8.8+dfsg/scripts/single.sh 2022-08-12 07:58:03.000000000 +0000 +++ toybox-0.8.9+dfsg/scripts/single.sh 2023-01-10 19:24:45.000000000 +0000 @@ -4,6 +4,8 @@ [ -z "$1" ] && { echo "usage: single.sh command..." >&2; exit 1; } +source scripts/portability.sh + # Add trailing / to PREFIX when it's set but hasn't got one [ "$PREFIX" == "${PREFIX%/}" ] && PREFIX="${PREFIX:+$PREFIX/}" @@ -17,7 +19,7 @@ # Force dependencies to rebuild headers if we build multiplexer after this. touch "$KCONFIG_CONFIG" fi -GLOBDEP="$(sed -n 's/CONFIG_\(TOYBOX_[^=]*\)=y/\1/p' "$KCONFIG_CONFIG")" +GLOBDEP="$($SED -n 's/CONFIG_\(TOYBOX_[^=]*\)=y/\1/p' "$KCONFIG_CONFIG")" KCONFIG_CONFIG=.singleconfig for i in "$@" @@ -36,15 +38,15 @@ unset DEPENDS MPDEL if [ "$i" == sh ] then - DEPENDS="$(sed -n 's/USE_\([^(]*\)(NEWTOY([^,]*,.*TOYFLAG_MAYFORK.*/\1/p' toys/*/*.c)" + DEPENDS="$($SED -n 's/USE_\([^(]*\)(NEWTOY([^,]*,.*TOYFLAG_MAYFORK.*/\1/p' toys/*/*.c)" else MPDEL='s/CONFIG_TOYBOX=y/# CONFIG_TOYBOX is not set/;t' fi # Enable stuff this command depends on - DEPENDS="$({ echo $DEPENDS $GLOBDEP; sed -n "/^config *$i"'$/,/^$/{s/^[ \t]*depends on //;T;s/[!][A-Z0-9_]*//g;s/ *&& */|/g;p}' $TOYFILE;}| xargs | tr ' ' '|')" + DEPENDS="$({ echo $DEPENDS $GLOBDEP; $SED -n "/^config *$i"'$/,/^$/{s/^[ \t]*depends on //;T;s/[!][A-Z0-9_]*//g;s/ *&& */|/g;p}' $TOYFILE;}| xargs | tr ' ' '|')" NAME=$(echo $i | tr a-z- A-Z_) - sed -ri -e "$MPDEL" \ + $SED -ri -e "$MPDEL" \ -e "s/# (CONFIG_($NAME|${NAME}_.*${DEPENDS:+|$DEPENDS})) is not set/\1=y/" \ "$KCONFIG_CONFIG" || exit 1 diff -Nru toybox-0.8.8+dfsg/scripts/test_mkroot.sh toybox-0.8.9+dfsg/scripts/test_mkroot.sh --- toybox-0.8.8+dfsg/scripts/test_mkroot.sh 1970-01-01 00:00:00.000000000 +0000 +++ toybox-0.8.9+dfsg/scripts/test_mkroot.sh 2023-01-10 19:24:45.000000000 +0000 @@ -0,0 +1,84 @@ +#!/bin/bash + +die() { echo "$@"; exit 1; } + +[ -n "$(which toybox)" -a -n "$(which mksquashfs)" ] || + die "Need toybox and mksquashfs in $PATH" + +mkdir -p "${TEST:=$PWD/root/build/test}" && + +# Setup test filesystem +cat > "$TEST"/init << 'EOF' && +#!/bin/sh + +echo +echo === init $HOST +[ "$(date +%s)" -gt 1500000000 ] && echo === date ok $HOST +wget http://10.0.2.2:65432 -O - +# TODO: cd /mnt && scripts/test.sh +EOF +chmod +x "$TEST"/init && + +mksquashfs "$TEST"/init configure scripts/ tests/ "$TEST"/init.sqf -noappend -all-root >/dev/null && + +# Setup for network smoke test +echo === net ok > "$TEST"/index.html || die "smoketest setup" +toybox netcat -p 65432 -s 127.0.0.1 -L toybox httpd "$TEST" & +trap "kill $!" EXIT +sleep .25 + +[ -n "$(toybox wget http://127.0.0.1:65432/ -O - | grep ===)" ] || die "wget" + +do_test() +{ + X=$(dirname "$1") Y=$(basename $X) + [ ! -e "$1" ] && { echo skip "$Y"; return 0; } + # Alas KARGS=quiet doesn't silence qemu's bios, so filter output ourselves. + # QEMU broke -hda because too many people know how to use it, this is + # the new edgier version they added to be -hda without gratuitous breakage. + { + cd $X || continue + echo === $X + # Can't point two QEMU instances at same sqf because gratuitous file locking + cp "$TEST"/init.{sqf,$BASHPID} && + # When stdin is a tty QEMU will SIGTTOU itself here, so &1" + rm -f "$TEST/init.$BASHPID" + cd ../.. + } | tee root/build/log/$Y-test.txt | { [ -z "$V" ] && cat >/dev/null || { [ "$V" -gt 1 ] && cat || grep '^=== '; } } +} + +# Just test targets on command line? +if [ $# -gt 0 ]; then + ((V++)) + for I in "$@"; do do_test root/"$I"/linux-kernel; done + exit +fi + +COUNT=0 CPUS=$(($(nproc)+0)) +for I in root/*/linux-kernel +do + do_test "$I" | { [ -z "$V" ] && cat >/dev/null || { [ "$V" -gt 1 ] && cat || grep '^=== '; } } & + [ $((++COUNT)) -ge $CPUS ] && + { wait -n; ((--COUNT)); [ -z "$V" ] && echo -n .; } +done + +while [ $COUNT -gt 0 ]; do + wait -n; ((--COUNT)); [ -z "$V" ] && echo -n . +done +echo + +PASS= NOPASS= +for I in root/*/linux-kernel +do + [ ! -e "$I" ] && continue + X=$(dirname $I) Y=$(basename $X) + + [ "$(grep '^=== ' root/build/log/$Y-test.txt | wc -l)" -eq 4 ] && + PASS+="$Y " || NOPASS+="$Y " +done + +[ -n "$PASS" ] && echo PASS=$PASS +[ -n "$NOPASS" ] && echo NOPASS=$NOPASS +X="$(ls root | egrep -xv "$(ls root/*/linux-kernel | sed 's@root/\([^/]*\)/linux-kernel@\1@' | tr '\n' '|')build" | xargs)" +[ -n "$X" ] && echo No kernel: $X diff -Nru toybox-0.8.8+dfsg/scripts/test.sh toybox-0.8.9+dfsg/scripts/test.sh --- toybox-0.8.8+dfsg/scripts/test.sh 2022-08-12 07:58:03.000000000 +0000 +++ toybox-0.8.9+dfsg/scripts/test.sh 2023-01-10 19:24:45.000000000 +0000 @@ -8,26 +8,28 @@ trap 'kill $(jobs -p) 2>/dev/null; exit 1' INT -rm -rf generated/testdir -mkdir -p generated/testdir/testdir +export PREFIX=generated/testdir +rm -rf "$PREFIX" +mkdir -p "$PREFIX"/testdir if [ -z "$TEST_HOST" ] then if [ $# -ne 0 ] then - PREFIX=generated/testdir/ scripts/single.sh "$@" || exit 1 + scripts/single.sh "$@" || exit 1 else - make install_flat PREFIX=generated/testdir || exit 1 + scripts/install.sh --symlink --force || exit 1 fi fi -cd generated/testdir +export -n PREFIX +cd "$PREFIX" PATH="$PWD:$PATH" TESTDIR="$PWD" export LC_COLLATE=C [ -f "$TOPDIR/generated/config.h" ] && - export OPTIONFLAGS=:$(echo $($SED -nr 's/^#define CFG_(.*) 1/\1/p' "$TOPDIR/generated/config.h") | $SED 's/ /:/g') + export OPTIONFLAGS=:$($SED -nr 's/^#define CFG_(.*) 1$/\1/p' "$TOPDIR/generated/config.h" | tr '\n' :) do_test() { @@ -38,7 +40,7 @@ if [ -z "$TEST_HOST" ] then C="$TESTDIR/$CMDNAME" - [ ! -e "$C" ] && echo "$CMDNAME disabled" && return + [ ! -e "$C" ] && echo "$SHOWSKIP: $CMDNAME disabled" && return C="$(dirname $(realpath "$C"))/$CMDNAME" else C="$(which $CMDNAME 2>/dev/null)" diff -Nru toybox-0.8.8+dfsg/tests/chattr.test toybox-0.8.9+dfsg/tests/chattr.test --- toybox-0.8.8+dfsg/tests/chattr.test 2022-08-12 07:58:03.000000000 +0000 +++ toybox-0.8.9+dfsg/tests/chattr.test 2023-01-10 19:24:45.000000000 +0000 @@ -51,7 +51,7 @@ # f2fs in 5.6+ kernels supports compression, but you can only enable # compression on a file while it's still empty, so we skip +c too. for attr in "A" "d" "e" "j" "P" "S" "s" "t" "u"; do - echo "$_t" > testFile && chattr +$attr testFile 2>/dev/null || SKIPNEXT=1 + echo "$_t" > testFile && chattr +$attr testFile 2>/dev/null || ((++SKIP)) # Check that $attr is in the lsattr output, then that - turns it back off. testing "toggle $attr" "lsattr testFile | awk '{print \$1}' > attrs; grep -q $attr attrs || cat attrs; cat testFile && chattr -$attr testFile && @@ -70,7 +70,7 @@ rm -rf testFile*; $OUT" \ "$_aA fileA\n$_aA fileB\n$_empty fileA\n$_empty fileB\n" "" "" -touch testFile; chattr -v 1234 testFile 2>/dev/null || SKIPNEXT=1 +touch testFile && chattr -v 1234 testFile 2>/dev/null || ((++SKIP)) testing "-v version" "lsattr -v testFile | awk '{print \$1}' && chattr -v 4567 testFile && lsattr -v testFile | awk '{print \$1}'; rm -rf testFile" "1234\n4567\n" "" "" diff -Nru toybox-0.8.8+dfsg/tests/chmod.test toybox-0.8.9+dfsg/tests/chmod.test --- toybox-0.8.8+dfsg/tests/chmod.test 2022-08-12 07:58:03.000000000 +0000 +++ toybox-0.8.9+dfsg/tests/chmod.test 2023-01-10 19:24:45.000000000 +0000 @@ -104,13 +104,13 @@ # macOS doesn't allow +s in /tmp touch s-supported -chmod +s s-supported 2>/dev/null || SKIP=1 +chmod +s s-supported 2>/dev/null || SKIP=99 rm s-supported chtest g+s "drwxr-sr-x\n-rw-r-Sr--\n" chtest u+s "drwsr-xr-x\n-rwSr--r--\n" chtest +s "drwsr-sr-x\n-rwSr-Sr--\n" chtest o+s "drwxr-xr-x\n-rw-r--r--\n" -unset SKIP +SKIP=0 chtest +t "drwxr-xr-t\n-rw-r--r-T\n" chtest a=r+w+x "drwxrwxrwx\n-rwxrwxrwx\n" diff -Nru toybox-0.8.8+dfsg/tests/comm.test toybox-0.8.9+dfsg/tests/comm.test --- toybox-0.8.8+dfsg/tests/comm.test 2022-08-12 07:58:03.000000000 +0000 +++ toybox-0.8.9+dfsg/tests/comm.test 2023-01-10 19:24:45.000000000 +0000 @@ -4,6 +4,9 @@ #testing "name" "command" "result" "infile" "stdin" -for i in a b c ; do echo $i >> lhs ; done +echo -e 'a\nb\nc'> lhs for i in c d e ; do echo $i >> rhs ; done -testing "comm" "comm lhs rhs" "a\nb\n\t\tc\n\td\n\te\n" "" "" +testing "comm" "comm lhs input" "a\nb\n\t\tc\n\td\n\te\n" "c\nd\ne\n" "" +testing "comm -" "comm - input" "a\nb\n\t\tc\n\td\n\te\n" "c\nd\ne\n" "a\nb\nc\n" +testing "comm -123 detects missing" "comm - missing 2>/dev/null || echo here" \ + "here\n" "" "" diff -Nru toybox-0.8.8+dfsg/tests/date.test toybox-0.8.9+dfsg/tests/date.test --- toybox-0.8.8+dfsg/tests/date.test 2022-08-12 07:58:03.000000000 +0000 +++ toybox-0.8.9+dfsg/tests/date.test 2023-01-10 19:24:45.000000000 +0000 @@ -97,3 +97,6 @@ # Can we parse date's own output format? testing "round trip" 'TZ=$tz date -d "$(TZ=$tz date -d @1598476818)"' \ "Wed Aug 26 23:20:18 CEST 2020\n" "" "" + +toyonly testcmd "-D with -d" "-uD '%s' -d '1234567890'" \ + "Fri Feb 13 23:31:30 UTC 2009\n" "" "" diff -Nru toybox-0.8.8+dfsg/tests/diff.test toybox-0.8.9+dfsg/tests/diff.test --- toybox-0.8.8+dfsg/tests/diff.test 2022-08-12 07:58:03.000000000 +0000 +++ toybox-0.8.9+dfsg/tests/diff.test 2023-01-10 19:24:45.000000000 +0000 @@ -55,6 +55,54 @@ +3 ' "" "" +echo -e 'int bar() { +} + +int foo() { +} + +int baz() { + 1 + {2 + 3 + 4 + foo +} +'> a +echo -e 'int barbar() { +} + +int foo() { +} + +int baz() { + 1a + {2 + 3 + 4 + bar +} +'> b +testcmd 'show function' "--show-function-line=' {$' -U1 -L lll -L rrr a b" \ +'--- lll ++++ rrr +@@ -1,2 +1,2 @@ +-int bar() { ++int barbar() { + } +@@ -7,3 +7,3 @@ int foo() { + int baz() { +- 1 ++ 1a + {2 +@@ -11,3 +11,3 @@ int baz() { + 4 +- foo ++ bar + } +' \ +'' '' + seq 1 100000 > one seq 1 4 100000 > two testcmd 'big hunk' '-u --label nope --label nope one two' \ diff -Nru toybox-0.8.8+dfsg/tests/grep.test toybox-0.8.9+dfsg/tests/grep.test --- toybox-0.8.8+dfsg/tests/grep.test 2022-08-12 07:58:03.000000000 +0000 +++ toybox-0.8.9+dfsg/tests/grep.test 2023-01-10 19:24:45.000000000 +0000 @@ -1,5 +1,7 @@ #!/bin/bash +# TODO: several tests need to check both fast and slow paths + [ -f testing.sh ] && . testing.sh # Copyright 2013 by Kyungsu Kim @@ -7,174 +9,165 @@ #testing "name" "command" "result" "infile" "stdin" -testing "-c" "grep -c 123 input" "3\n" "123\ncount 123\n123\nfasdfasdf" "" +testcmd "-c" "-c 123 input" "3\n" "123\ncount 123\n123\nfasdfasdf" "" echo -e "this is test" > foo echo -e "this is test2" > foo2 echo -e "this is foo3" > foo3 -testing "-l" "grep -l test foo foo2 foo3" "foo\nfoo2\n" "" "" -testing "-L" "grep -L test foo foo2 foo3" "foo3\n" "" "" +testcmd "-l" "-l test foo foo2 foo3" "foo\nfoo2\n" "" "" +testcmd "-L" "-L test foo foo2 foo3" "foo3\n" "" "" rm foo foo2 foo3 -testing "-q" "grep -q test input && echo yes" "yes\n" "this is a test\n" "" -testing "-E" "grep -E '[0-9]' input" "1234123asdfas123123\n1\n" \ +testcmd "-q" "-q test input && echo yes" "yes\n" "this is a test\n" "" +testcmd "-E" "-E '[0-9]' input" "1234123asdfas123123\n1\n" \ "1234123asdfas123123\nabc\n1\nabcde" "" -testing "-e" "grep -e '[0-9]' input" "1234123asdfas123123\n1\n" \ +testcmd "-e" "-e '[0-9]' input" "1234123asdfas123123\n1\n" \ "1234123asdfas123123\nabc\n1\nabcde" "" -testing "-e -e" "grep -e one -e two -e three input" \ +testcmd "-e -e" "-e one -e two -e three input" \ "two\ntwo\nthree\none\n" "two\ntwo\nthree\nand\none\n" "" -testing "-F" "grep -F is input" "this is test\nthis is test2\n" \ +testcmd "-F" "-F is input" "this is test\nthis is test2\n" \ "this is test\nthis is test2\ntest case" "" +testcmd "-Fo ''" "-Fo ''" "" "" "hello\n" +testcmd "-Fw ''" "-Fw ''" "" "" "hello\n" +testcmd "-Fw '' 2" "-Fw ''" "\n" "" "\n" +testcmd "-F is really fixed" "-F '.[x]'" "c.[x]d\n" "" "axb\nc.[x]d\n" echo -e "this is test\nthis is test2\ntest case" > foo echo -e "hello this is test" > foo2 echo -e "hi hello" > foo3 -testing "-H" "grep -H is foo foo2 foo3" "foo:this is test\nfoo:this is test2\nfoo2:hello this is test\n" "" "" +testcmd "-H" "-H is foo foo2 foo3" \ + "foo:this is test\nfoo:this is test2\nfoo2:hello this is test\n" "" "" rm foo foo2 foo3 -testing "-b" "grep -b is input" "0:this is test\n13:this is test2\n" \ +testcmd "-b" "-b is input" "0:this is test\n13:this is test2\n" \ "this is test\nthis is test2\ntest case" "" -testing "-i" "grep -i is input" "thisIs test\nthis is test2\n" \ +testcmd "-i" "-i is input" "thisIs test\nthis is test2\n" \ "thisIs test\nthis is test2\ntest case" "" -testing "-n" "grep -n is input" "1:this is test\n2:this is test2\n" \ +testcmd "-n" "-n is input" "1:this is test\n2:this is test2\n" \ "this is test\nthis is test2\ntest case" "" -testing "-o" "grep -o is input" "is\nis\nis\nis\n" \ +testcmd "-o" "-o is input" "is\nis\nis\nis\n" \ "this is test\nthis is test2\ntest case" "" -testing "-s" "grep -hs hello asdf input 2>&1" "hello\n" "hello\n" "" -testing "-v" "grep -v abc input" "1234123asdfas123123\n1ABa\n" \ +testcmd "-s" "-hs hello asdf input 2>&1" "hello\n" "hello\n" "" +testcmd "-v" "-v abc input" "1234123asdfas123123\n1ABa\n" \ "1234123asdfas123123\n1ABabc\nabc\n1ABa\nabcde" "" -testing "-w" "grep -w abc input" "abc\n123 abc\nabc 123\n123 abc 456\n" \ +testcmd "-w" "-w abc input" "abc\n123 abc\nabc 123\n123 abc 456\n" \ "1234123asdfas123123\n1ABabc\nabc\n1ABa\nabcde\n123 abc\nabc 123\n123 abc 456\n" "" -testing "-x" "grep -x abc input" "abc\n" \ - "aabcc\nabc\n" "" +testcmd "-x" "-x abc input" "abc\n" "aabcc\nabc\n" "" -testing "-H (standard input)" "grep -H abc" "(standard input):abc\n" \ - "" "abc\n" -testing "-l (standard input)" "grep -l abc" "(standard input)\n" \ - "" "abc\n" -testing "-n two inputs" "grep -hn def - input" "2:def\n2:def\n" \ +testcmd "-H (standard input)" "-H abc" "(standard input):abc\n" "" "abc\n" +testcmd "-l (standard input)" "-l abc" "(standard input)\n" "" "abc\n" +testcmd "-n two inputs" "-hn def - input" "2:def\n2:def\n" \ "abc\ndef\n" "abc\ndef\n" -testing "pattern with newline" "grep 'abc -def' input" "aabcc\nddeff\n" \ +testcmd "pattern with newline" $'"abc\ndef" input' "aabcc\nddeff\n" \ "aaaa\naabcc\n\dddd\nddeff\nffff\n" "" -testing "-lH" "grep -lH abc input" "input\n" "abc\n" "" -testing "-cn" "grep -cn abc input" "1\n" "abc\n" "" -testing "-cH" "grep -cH abc input" "input:1\n" "abc\n" "" -testing "-qs" "grep -qs abc none input && echo yes" "yes\n" "abc\n" "" -testing "-hl" "grep -hl abc input" "input\n" "abc\n" "" -testing "-b stdin" "grep -b one" "0:one\n4:one\n8:one\n" "" "one\none\none\n" -testing "-o overlap" "grep -bo aaa" "1:aaa\n" "" "baaaa\n" +testcmd "-lH" "-lH abc input" "input\n" "abc\n" "" +testcmd "-cn" "-cn abc input" "1\n" "abc\n" "" +testcmd "-cH" "-cH abc input" "input:1\n" "abc\n" "" +testcmd "-qs" "-qs abc none input && echo yes" "yes\n" "abc\n" "" +testcmd "-hl" "-hl abc input" "input\n" "abc\n" "" +testcmd "-b stdin" "-b one" "0:one\n4:one\n8:one\n" "" "one\none\none\n" +testcmd "-o overlap" "-bo aaa" "1:aaa\n" "" "baaaa\n" # nonobvious: -co counts lines, not matches -testing "-co" "grep -co one input" "1\n" "one one one\n" "" -testing "-nom" "grep -nom 2 one" "1:one\n1:one\n1:one\n2:one\n2:one\n" \ +testcmd "-co" "-co one input" "1\n" "one one one\n" "" +testcmd "-nom" "-nom 2 one" "1:one\n1:one\n1:one\n2:one\n2:one\n" \ "" "one one one\none one\none" -toyonly testing "-vo" "grep -vo one input" "two\nthree\n" "onetwoonethreeone\n" "" -testing "no newline" "grep -h one input -" \ +toyonly testcmd "-vo" "-vo one input" "two\nthree\n" "onetwoonethreeone\n" "" +testcmd "no newline" "-h one input -" \ "hello one\nthere one\n" "hello one" "there one" -testing "-e multi" "grep -e one -ethree input" \ - "three\none\n" "three\ntwo\none\n" "" +testcmd "-e multi" "-e one -ethree input" "three\none\n" "three\ntwo\none\n" "" # Suppress filenames for recursive test because dunno order they'd occur in mkdir sub echo -e "one\ntwo\nthree" > sub/one echo -e "three\ntwo\none" > sub/two -testing "-hr" "grep -hr one sub" "one\none\n" "" "" -testing "-r file" "grep -r three sub/two" "three\n" "" "" -testing "-r dir" "grep -r one sub | sort" "sub/one:one\nsub/two:one\n" \ +testcmd "-hr" "-hr one sub" "one\none\n" "" "" +testcmd "-r file" "-r three sub/two" "three\n" "" "" +testcmd "-r dir" "-r one sub | sort" "sub/one:one\nsub/two:one\n" \ "" "" rm -rf sub # -x exact match overrides -F's "empty string matches whole line" behavior -testing "-Fx ''" "grep -Fx '' input" "" "one one one\n" "" -testing "-F ''" "grep -F '' input" "one one one\n" "one one one\n" "" -testing "-F -e blah -e ''" "grep -F -e blah -e '' input" "one one one\n" \ - "one one one\n" "" -testing "-Fxv -e subset" "grep -Fxv -e bbswitch-dkms -e dkms" "" "" \ - "bbswitch-dkms\n" -testing "-e blah -e ''" "grep -e blah -e '' input" "one one one\n" \ +testcmd "-Fx ''" "-Fx '' input" "" "one one one\n" "" +testcmd "-F ''" "-F '' input" "one one one\n" "one one one\n" "" +testcmd "-F -e blah -e ''" "-F -e blah -e '' input" "one one one\n" \ "one one one\n" "" -testing "-w ''" "grep -w '' input" "" "one one one\n" "" -testing "-w '' 2" "grep -w '' input" "one two\n" "one two\n" "" -testing "-w \\1" "grep -wo '\\(x\\)\\1'" "xx\n" "" "xx" -testing "-o ''" "grep -o '' input" "" "one one one\n" "" -testing "backref" 'grep -e "a\(b\)" -e "b\(c\)\1"' "bcc\nab\n" \ - "" "bcc\nbcb\nab\n" +testcmd "-Fxv -e subset" "-Fxv -e bbswitch-dkms -e dkms" "" "" "bbswitch-dkms\n" +testcmd "-e blah -e ''" "-e blah -e '' input" "one one one\n" "one one one\n" "" +testcmd "-w ''" "-w '' input" "" "one one one\n" "" +testcmd "-w '' 2" "-w '' input" "one two\n" "one two\n" "" +testcmd "-w \\1" "-wo '\\(x\\)\\1'" "xx\n" "" "xx" +testcmd "-o ''" "-o '' input" "" "one one one\n" "" +testcmd "backref" '-e "a\(b\)" -e "b\(c\)\1"' "bcc\nab\n" "" "bcc\nbcb\nab\n" -testing "-A" "grep -A 2 yes" "yes\nno\nno\n--\nyes\nno\nno\nyes\nno\n" \ +testcmd "-A" "-A 2 yes" "yes\nno\nno\n--\nyes\nno\nno\nyes\nno\n" \ "" "yes\nno\nno\nno\nyes\nno\nno\nyes\nno" -testing "-B" "grep -B 1 yes" "no\nyes\n--\nno\nyes\nno\nyes\n" \ +testcmd "-B" "-B 1 yes" "no\nyes\n--\nno\nyes\nno\nyes\n" \ "" "no\nno\nno\nyes\nno\nno\nyes\nno\nyes" -testing "-C" "grep -C 1 yes" \ - "yes\nno\n--\nno\nyes\nno\nno\nyes\nno\nyes\nno\n" \ +testcmd "-C" "-C 1 yes" "yes\nno\n--\nno\nyes\nno\nno\nyes\nno\nyes\nno\n" \ "" "yes\nno\nno\nno\nyes\nno\nno\nyes\nno\nyes\nno\nno" -testing "-HnC" "grep -HnC1 two" \ +testcmd "-HnC" "-HnC1 two" \ "(standard input)-1-one\n(standard input):2:two\n(standard input)-3-three\n" \ "" "one\ntwo\nthree" # Context lines weren't showing -b -testing "-HnbB1" "grep -HnbB1 f input" \ +testcmd "-HnbB1" "-HnbB1 f input" \ "input-3-8-three\ninput:4:14:four\ninput:5:19:five\n" \ "one\ntwo\nthree\nfour\nfive\n" "" -testing "-q match overrides error" \ - "grep -q hello missing input 2>/dev/null && echo yes" "yes\n" "hello\n" "" -testing "-q not found is 1" \ - 'grep -q hello input || echo $?' "1\n" "x" "" -testing "-q missing is 2" \ - 'grep -q hello missing missing 2>/dev/null || echo $?' "2\n" "" "" -testing "-q missing survives exists but not found" \ - 'grep -q hello missing missing input 2>/dev/null || echo $?' "2\n" "" "" -testing "not found retained past match" \ - 'grep hello missing input 2>/dev/null || echo $?' \ - "input:hello\n2\n" "hello\n" "" +testcmd "-q match overrides error" \ + "-q hello missing input 2>/dev/null && echo yes" "yes\n" "hello\n" "" +testcmd "-q not found is 1" '-q hello input || echo $?' "1\n" "x" "" +testcmd "-q missing is 2" \ + '-q hello missing missing 2>/dev/null || echo $?' "2\n" "" "" +testcmd "-q missing survives exists but not found" \ + '-q hello missing missing input 2>/dev/null || echo $?' "2\n" "" "" +testcmd "not found retained past match" \ + 'hello missing input 2>/dev/null || echo $?' "input:hello\n2\n" "hello\n" "" touch empty -testing "one match good enough for 0" \ - 'grep hello input empty && echo $?' 'input:hello\n0\n' 'hello\n' '' +testcmd "one match good enough for 0" \ + 'hello input empty && echo $?' 'input:hello\n0\n' 'hello\n' '' rm empty -testing "-o ''" "grep -o ''" "" "" "one two three\none two\none\n" -testing '' "grep -o -e '' -e two" "two\ntwo\n" "" \ - "one two three\none two\none\n" +testcmd "-o ''" "-o ''" "" "" "one two three\none two\none\n" +testcmd '' "-o -e '' -e two" "two\ntwo\n" "" "one two three\none two\none\n" echo "one\ntwo\nthree" > test -testing "-l overrides -C" "grep -l -C1 two test input" "test\ninput\n" \ +testcmd "-l overrides -C" "-l -C1 two test input" "test\ninput\n" \ "three\ntwo\none\n" "" rm test # match after NUL byte -testing "match after NUL byte" "grep -a two" "one\0and two three\n" \ +testcmd "match after NUL byte" "-a two" "one\0and two three\n" \ "" 'one\0and two three' # BREs versus EREs -testing "implicit BRE |" "grep 'uno|dos'" "uno|dos\n" \ - "" "uno\ndos\nuno|dos\n" -testing "explicit BRE |" "grep -e 'uno|dos'" "uno|dos\n" \ - "" "uno\ndos\nuno|dos\n" -testing "explicit ERE |" "grep -E 'uno|dos'" "uno\ndos\nuno|dos\n" \ +testcmd "implicit BRE |" "'uno|dos'" "uno|dos\n" "" "uno\ndos\nuno|dos\n" +testcmd "explicit BRE |" "-e 'uno|dos'" "uno|dos\n" "" "uno\ndos\nuno|dos\n" +testcmd "explicit ERE |" "-E 'uno|dos'" "uno\ndos\nuno|dos\n" \ "" "uno\ndos\nuno|dos\n" -testing "" "grep -o -e iss -e ipp" "iss\niss\nipp\n" "" "mississippi" -testing "" "grep -o -e gum -e rgu" "rgu\n" "" "argument" +testcmd "" "-o -e iss -e ipp" "iss\niss\nipp\n" "" "mississippi" +testcmd "" "-o -e gum -e rgu" "rgu\n" "" "argument" -testing "early failure" 'grep --what 2>/dev/null || echo $?' "2\n" "" "" +testcmd "early failure" '--what 2>/dev/null || echo $?' "2\n" "" "" -testing "" 'grep abc ; echo $?' "abcdef\n0\n" "" "abcdef\n" -testing "" 'grep abc doesnotexist input 2>/dev/null; echo $?' \ +testcmd "" 'abc ; echo $?' "abcdef\n0\n" "" "abcdef\n" +testcmd "" 'abc doesnotexist input 2>/dev/null; echo $?' \ "input:abcdef\n2\n" "abcdef\n" "" mkdir sub ln -s nope sub/link -testing "" 'grep -r walrus sub 2>/dev/null; echo $?' "1\n" "" "" +testcmd "" '-r walrus sub 2>/dev/null; echo $?' "1\n" "" "" rm -rf sub # --exclude-dir -mkdir sub -mkdir sub/yes +mkdir -p sub/yes echo "hello world" > sub/yes/test mkdir sub/no echo "hello world" > sub/no/test -testing "--exclude-dir" 'grep --exclude-dir=no -r world sub' "sub/yes/test:hello world\n" "" "" +testcmd "--exclude-dir" '--exclude-dir=no -r world sub' \ + "sub/yes/test:hello world\n" "" "" rm -rf sub # -r and -R differ in that -R will dereference symlinks to directories. @@ -182,28 +175,52 @@ echo "hello" > dir/f mkdir sub ln -s ../dir sub/link -testing "" "grep -rh hello sub 2>/dev/null || echo err" "err\n" "" "" -testing "" "grep -Rh hello sub" "hello\n" "" "" +testcmd "" "-rh hello sub 2>/dev/null || echo err" "err\n" "" "" +testcmd "" "-Rh hello sub" "hello\n" "" "" rm -rf sub real # -F multiple matches -testing "-F multiple" "grep -F h input" "this is hello\nthis is world\n" \ +testcmd "-F multiple" "-F h input" "this is hello\nthis is world\n" \ "missing\nthis is hello\nthis is world\nmissing" "" -testing "-Fi multiple" "grep -Fi h input" "this is HELLO\nthis is WORLD\n" \ +testcmd "-Fi multiple" "-Fi h input" "this is HELLO\nthis is WORLD\n" \ "missing\nthis is HELLO\nthis is WORLD\nmissing" "" -testing "-F empty multiple" "grep -Fi '' input" \ +testcmd "-F empty multiple" "-Fi '' input" \ "missing\nthis is HELLO\nthis is WORLD\nmissing\n" \ "missing\nthis is HELLO\nthis is WORLD\nmissing" "" -testing "-Fx" "grep -Fx h input" "h\n" \ +testcmd "-Fx" "-Fx h input" "h\n" \ "missing\nH\nthis is hello\nthis is world\nh\nmissing" "" -testing "-Fix" "grep -Fix h input" "H\nh\n" \ +testcmd "-Fix" "-Fix h input" "H\nh\n" \ "missing\nH\nthis is HELLO\nthis is WORLD\nh\nmissing" "" -testing "-f /dev/null" "grep -f /dev/null" "" "" "hello\n" -testing "-z with \n in pattern" "grep -f input" "hi\nthere\n" "i\nt" "hi\nthere" +testcmd "-F bucket sort" "-F '\.zip'" '\\.zip\n' '' '\\.zip\n' +testcmd "-f /dev/null" "-f /dev/null" "" "" "hello\n" + +# -z doesn't apply to the \n in -e or -f patterns +# Because x\n becomes "x" and "" the second of which matches every line. +testcmd '-z patter\n' "-ze $'x\n' | xxd -pc0" \ + '6f6e650a74776f0a74687265650a00\n' '' 'one\ntwo\nthree\n' +testcmd "-z patter\n 2" "-zof input | xxd -pc0" "69007400\n" "i\nt" "hi\nthere" +testcmd '-lZ' '-lZ ^t input' 'input\0' 'one\ntwo' '' -testing "print zero length match" "grep '[0-9]*'" "abc\n" "" "abc\n" -testing "-o skip zero length match" "grep -o '[0-9]*'" "1234\n" "" "a1234b" +# other implementations get this wrong without -a, but right with -a??? +toyonly testcmd '-l ^ after \0' '-l ^t' '' 'one\0two' '' + +testcmd "print zero length match" "'[0-9]*'" "abc\n" "" "abc\n" +testcmd "-o skip zero length match" "-o '[0-9]*'" "1234\n" "" "a1234b" # Bit of a hack, but other greps insert gratuitous \e[K clear-to-EOL -testing "--color highlights all matches" \ - "grep --color=always def | grep -o '[[][0-9;]*[Km]def.[[]m' | wc -l" \ +testcmd "--color highlights all matches" \ + "--color=always def | grep -o '[[][0-9;]*[Km]def.[[]m' | wc -l" \ "2\n" "" "abcdefghidefjkl\n" +seq 1 100002 | base64 > testfile +testing "speed" "timeout 5 grep -f testfile testfile 2>/dev/null | wc -l" \ + "10332\n" "" "" +rm -f testfile + +# Fast path tests + +testcmd 'initial \' '\\.jar' 'bell.jar\n' '' 'bell.jar\n' +testcmd '^$' '^\$' '\n' '' 'one\n\ntwo\n' +testcmd 'middle ^ not special' 'a^' 'a^b\n' '' 'a^b\nb^a\n' +# Quoted to protect it from the shell, grep should just see '$b' +testcmd 'middle $ not special' "'\$b'" 'a$b\n' '' 'a$b\nb$a\n' + +testcmd 'grep -of' '-of input' 'abc\n' 'a.c\n' 'abcdef\n' diff -Nru toybox-0.8.8+dfsg/tests/lsattr.test toybox-0.8.9+dfsg/tests/lsattr.test --- toybox-0.8.8+dfsg/tests/lsattr.test 2022-08-12 07:58:03.000000000 +0000 +++ toybox-0.8.9+dfsg/tests/lsattr.test 2023-01-10 19:24:45.000000000 +0000 @@ -31,12 +31,12 @@ NOSPACE=1 testing "-lv file" "lsattr -lv file | clean" "_ file No_Atime\n" "" "" # You need at least Linux 4.5 plus file system support for project ids. -lsattr -p file >/dev/null 2>&1 || SKIPNEXT=1 -NOSPACE=1 testing "-p file" "lsattr -p file | clean" "_ $_A file\n" "" "" -lsattr -p file >/dev/null 2>&1 || SKIPNEXT=1 -NOSPACE=1 testing "-lp file" "lsattr -lp file | clean" "_ file No_Atime\n" "" "" -lsattr -p file >/dev/null 2>&1 || SKIPNEXT=1 -NOSPACE=1 testing "-vp file" "lsattr -vp file | clean" "_ _ $_A file\n" "" "" +lsattr -p file >/dev/null 2>&1 || SKIP=999 + NOSPACE=1 testing "-p file" "lsattr -p file | clean" "_ $_A file\n" "" "" + NOSPACE=1 testing "-lp file" "lsattr -lp file | clean" "_ file No_Atime\n" \ + "" "" + NOSPACE=1 testing "-vp file" "lsattr -vp file | clean" "_ _ $_A file\n" "" "" +SKIP=0 chattr -AacDdijsStTu file && cd .. rm -rf dir diff -Nru toybox-0.8.8+dfsg/tests/ls.test toybox-0.8.9+dfsg/tests/ls.test --- toybox-0.8.8+dfsg/tests/ls.test 2022-08-12 07:58:03.000000000 +0000 +++ toybox-0.8.9+dfsg/tests/ls.test 2023-01-10 19:24:45.000000000 +0000 @@ -68,5 +68,12 @@ testing "-w test 3" "$IN && ls -Cw 3; $OUT" "a\nb\nc\nd\ne\nf\n" "" "" testing "-w test 4" "$IN && ls -Cw 4; $OUT" "a d\nb e\nc f\n" "" "" +rm -rf lstest/* +touch lstest/'hello world' +testing "default escaping" "$IN && ls; $OUT" "hello world\n" "" "" +testing "-b" "$IN && ls -b; $OUT" 'hello\\ \\rworld\n' "" "" +testing "-q" "$IN && ls -q; $OUT" 'hello ?world\n' "" "" +testing "-N" "$IN && ls -q; $OUT" 'hello ?world\n' "" "" + # Removing test dir for cleanup purpose rm -rf lstest diff -Nru toybox-0.8.8+dfsg/tests/modinfo.test toybox-0.8.9+dfsg/tests/modinfo.test --- toybox-0.8.8+dfsg/tests/modinfo.test 2022-08-12 07:58:03.000000000 +0000 +++ toybox-0.8.9+dfsg/tests/modinfo.test 2023-01-10 19:24:45.000000000 +0000 @@ -17,9 +17,9 @@ testcmd "missing" "missing 2>&1" "modinfo: missing: not found\n" "" "" # Find some modules to work with. -MODULE_PATH1=$(find $MODULE_ROOT/lib/modules -name *.ko | head -1 2>/dev/null) +MODULE_PATH1=$(find $MODULE_ROOT/lib/modules/ -name *.ko | head -1 2>/dev/null) MODULE1=$(basename -s .ko $MODULE_PATH1) -MODULE_PATH2=$(find $MODULE_ROOT/lib/modules -name *.ko | head -2 | tail -1 2>/dev/null) +MODULE_PATH2=$(find $MODULE_ROOT/lib/modules/ -name *.ko | head -2 | tail -1 2>/dev/null) MODULE2=$(basename -s .ko $MODULE_PATH2) DASH_MODULE=$(basename -s .ko \ $(find $MODULE_ROOT/lib/modules -name *-*.ko | tail -1 2>/dev/null)) diff -Nru toybox-0.8.8+dfsg/tests/patch.test toybox-0.8.9+dfsg/tests/patch.test --- toybox-0.8.8+dfsg/tests/patch.test 2022-08-12 07:58:03.000000000 +0000 +++ toybox-0.8.9+dfsg/tests/patch.test 2023-01-10 19:24:45.000000000 +0000 @@ -4,6 +4,14 @@ #testing "name" "command" "result" "infile" "stdin" +testing "dry run doesn't create file" \ + "patch --dry-run >/dev/null && [ ! -e bork ] && echo yes" "yes\n" "" " +--- /dev/null ++++ bork +@@ -0,0 +1,1 @@ ++one +" + testing "create file" "patch >/dev/null && cat bork" "one\ntwo\nthree\n" "" " --- /dev/null +++ bork @@ -12,6 +20,7 @@ +two +three " + testing "insert in middle" "patch > /dev/null && cat bork" \ "one\nfour\ntwo\nthree\n" "" " --- bork @@ -85,6 +94,23 @@ @@ -0,0 +1 @@ +hello ' + +testing "dry run doesn't delete file" \ + "patch --dry-run > /dev/null && [ -e 'fruit bat' ] && echo yes" "yes\n" "" ' +--- "fruit bat" ++++ /dev/null +@@ -1 +0,0 @@ +-hello +' + +testing "delete file" \ + "patch > /dev/null && [ ! -e 'fruit bat' ] && echo yes" "yes\n" "" ' +--- "fruit bat" ++++ /dev/null +@@ -1 +0,0 @@ +-hello +' + # todo bork bork2 # We hit a bug, test the bugfix. diff -Nru toybox-0.8.8+dfsg/tests/printf.test toybox-0.8.9+dfsg/tests/printf.test --- toybox-0.8.8+dfsg/tests/printf.test 2022-08-12 07:58:03.000000000 +0000 +++ toybox-0.8.9+dfsg/tests/printf.test 2023-01-10 19:24:45.000000000 +0000 @@ -7,79 +7,74 @@ #testing "name" "command" "result" "infile" "stdin" -# Disable shell builtin -PRINTF="$(which printf)" +# Note: must use "testcmd" not "testing" else it's testing the shell builtin. -testing "text" "$PRINTF TEXT" "TEXT" "" "" +testcmd "text" "TEXT" "TEXT" "" "" # TODO: we have to use \x1b rather than \e in the expectations because # the Mac is stuck on bash 3.2 which doesn't support \e. This can go # away when we have a usable toysh. -testing "escapes" "$PRINTF 'one\ntwo\n\v\t\r\f\e\b\athree'" \ +testcmd "escapes" "'one\ntwo\n\v\t\r\f\e\b\athree'" \ "one\ntwo\n\v\t\r\f\x1b\b\athree" "" "" -testing "%b escapes" "$PRINTF %b 'one\ntwo\n\v\t\r\f\e\b\athree'" \ +testcmd "%b escapes" "%b 'one\ntwo\n\v\t\r\f\e\b\athree'" \ "one\ntwo\n\v\t\r\f\x1b\b\athree" "" "" -testing "null" "$PRINTF 'x\0y' | od -An -tx1" ' 78 00 79\n' "" "" -testing "trailing slash" "$PRINTF 'abc\'" 'abc\' "" "" -testing "octal" "$PRINTF ' \1\002\429\045x'" ' \001\002"9%x' "" "" -testing "not octal" "$PRINTF '\9'" '\9' "" "" -testing "hex" "$PRINTF 'A\x1b\x2B\x3Q\xa' | od -An -tx1" \ - ' 41 1b 2b 03 51 0a\n' "" "" -testing "%x" "$PRINTF '%x\n' 0x2a" "2a\n" "" "" - -testing "%d 42" "$PRINTF %d 42" "42" "" "" -testing "%d 0x2a" "$PRINTF %d 0x2a" "42" "" "" -testing "%d 052" "$PRINTF %d 052" "42" "" "" -testing "%d none" "$PRINTF %d" "0" "" "" -testing "%d null" "$PRINTF %d ''" "0" "" "" +testcmd "null" "'x\0y' | od -An -tx1" ' 78 00 79\n' "" "" +testcmd "trailing slash" "'abc\'" 'abc\' "" "" +testcmd "octal" "' \1\002\429\045x'" ' \001\002"9%x' "" "" +testcmd "not octal" "'\9'" '\9' "" "" +testcmd "hex" "'A\x1b\x2B\x3Q\xa' | od -An -tx1" ' 41 1b 2b 03 51 0a\n' "" "" +testcmd "%x" "'%x\n' 0x2a" "2a\n" "" "" + +testcmd "%d 42" "%d 42" "42" "" "" +testcmd "%d 0x2a" "%d 0x2a" "42" "" "" +testcmd "%d 052" "%d 052" "42" "" "" +testcmd "%d none" "%d" "0" "" "" +testcmd "%d null" "%d ''" "0" "" "" -testing "%s width precision" \ - "$PRINTF '%3s,%.3s,%10s,%10.3s' abcde fghij klmno pqrst" \ +testcmd "%s width precision" "'%3s,%.3s,%10s,%10.3s' abcde fghij klmno pqrst" \ "abcde,fgh, klmno, pqr" "" "" # posix: "The format operand shall be reused as often as necessary to satisfy # the argument operands." -testing "extra args" "$PRINTF 'abc%s!%ddef\n' X 42 ARG 36" \ +testcmd "extra args" "'abc%s!%ddef\n' X 42 ARG 36" \ "abcX!42def\nabcARG!36def\n" "" "" -testing "'%3c'" "$PRINTF '%3c' x" " x" "" "" -testing "'%-3c'" "$PRINTF '%-3c' x" "x " "" "" -testing "'%+d'" "$PRINTF '%+d' 5" "+5" "" "" - -testing "'%5d%4d' 1 21 321 4321 54321" \ - "$PRINTF '%5d%4d' 1 21 321 4321 54321" " 1 21 321432154321 0" "" "" -testing "'%c %c' 78 79" "$PRINTF '%c %c' 78 79" "7 7" "" "" -testing "'%d %d' 78 79" "$PRINTF '%d %d' 78 79" "78 79" "" "" -testing "'%f %f' 78 79" "$PRINTF '%f %f' 78 79" \ - "78.000000 79.000000" "" "" -testing "'f f' 78 79" "$PRINTF 'f f' 78 79 2>/dev/null" "f f" "" "" -testing "'%i %i' 78 79" "$PRINTF '%i %i' 78 79" "78 79" "" "" -testing "'%o %o' 78 79" "$PRINTF '%o %o' 78 79" "116 117" "" "" -testing "'%u %u' 78 79" "$PRINTF '%u %u' 78 79" "78 79" "" "" -testing "'%u %u' -1 -2" "$PRINTF '%u %u' -1 -2" \ +testcmd "'%3c'" "'%3c' x" " x" "" "" +testcmd "'%-3c'" "'%-3c' x" "x " "" "" +testcmd "'%+d'" "'%+d' 5" "+5" "" "" + +testcmd "'%5d%4d' 1 21 321 4321 54321" \ + "'%5d%4d' 1 21 321 4321 54321" " 1 21 321432154321 0" "" "" +testcmd "'%c %c' 78 79" "'%c %c' 78 79" "7 7" "" "" +testcmd "'%d %d' 78 79" "'%d %d' 78 79" "78 79" "" "" +testcmd "'%f %f' 78 79" "'%f %f' 78 79" "78.000000 79.000000" "" "" +testcmd "'f f' 78 79" "'f f' 78 79 2>/dev/null" "f f" "" "" +testcmd "'%i %i' 78 79" "'%i %i' 78 79" "78 79" "" "" +testcmd "'%o %o' 78 79" "'%o %o' 78 79" "116 117" "" "" +testcmd "'%u %u' 78 79" "'%u %u' 78 79" "78 79" "" "" +testcmd "'%u %u' -1 -2" "'%u %u' -1 -2" \ "18446744073709551615 18446744073709551614" "" "" -testing "'%x %X' 78 79" "$PRINTF '%x %X' 78 79" "4e 4F" "" "" -testing "'%g %G' 78 79" "$PRINTF '%g %G' 78 79" "78 79" "" "" -testing "'%s %s' 78 79" "$PRINTF '%s %s' 78 79" "78 79" "" "" +testcmd "'%x %X' 78 79" "'%x %X' 78 79" "4e 4F" "" "" +testcmd "'%g %G' 78 79" "'%g %G' 78 79" "78 79" "" "" +testcmd "'%s %s' 78 79" "'%s %s' 78 79" "78 79" "" "" -testing "%.s acts like %.0s" "$PRINTF %.s_ 1 2 3 4 5" "_____" "" "" -testing "corner case" "$PRINTF '\\8'" '\8' '' '' +testcmd "%.s acts like %.0s" "%.s_ 1 2 3 4 5" "_____" "" "" +testcmd "corner case" "'\\8'" '\8' '' '' # The posix spec explicitly specifies inconsistent behavior, # so treating the \0066 in %b like the \0066 not in %b is wrong because posix. -testing "printf posix inconsistency" "$PRINTF '\\0066-%b' '\\0066'" "\x066-6" \ - "" "" +testcmd "posix inconsistency" "'\\0066-%b' '\\0066'" "\x066-6" "" "" -testing "printf \x" "$PRINTF 'A\x1b\x2B\x3Q\xa' | od -An -tx1" \ - " 41 1b 2b 03 51 0a\n" "" "" +testcmd '\x' "'A\x1b\x2B\x3Q\xa' | od -An -tx1" " 41 1b 2b 03 51 0a\n" \ + "" "" -testing "printf \c" "$PRINTF 'one\ctwo'" "one" "" "" +testcmd '\c' "'one\ctwo'" "one" "" "" # An extra leading 0 is fine for %b, but not as a direct escape, for some # reason... -testing "printf octal %b" "$PRINTF '\0007%b' '\0007' | xxd -p" "003707\n" "" "" +testcmd "octal %b" "'\0007%b' '\0007' | xxd -p" "003707\n" "" "" # Unlike echo, printf errors out on bad hex. testcmd "invalid hex 1" "'one\xvdtwo' 2>/dev/null || echo err" "oneerr\n" "" "" diff -Nru toybox-0.8.8+dfsg/tests/readlink.test toybox-0.8.9+dfsg/tests/readlink.test --- toybox-0.8.8+dfsg/tests/readlink.test 2022-08-12 07:58:03.000000000 +0000 +++ toybox-0.8.9+dfsg/tests/readlink.test 2023-01-10 19:24:45.000000000 +0000 @@ -31,13 +31,17 @@ testing "-f link->link (recursive)" \ "readlink -f link 2>/dev/null || echo yes" "yes\n" "" "" -testing "-q notlink" "readlink -q file || echo yes" "yes\n" "" "" -testing "-q link" "readlink -q link && echo yes" "yes\n" "" "" +testing "-q notlink" "readlink -q file 2>&1 || echo yes" "yes\n" "" "" +testing "-q link" "readlink -q link && echo yes" "link\nyes\n" "" "" testing "-q notfound" "readlink -q notfound || echo yes" "yes\n" "" "" testing "-e found" "readlink -e file" "$APWD/file\n" "" "" testing "-e notfound" \ "readlink -e notfound 2>/dev/null || echo yes" "yes\n" "" "" testing "-nf ." "readlink -nf ." "$APWD" "" "" +# -n means no newline at _end_. I.E. on last argument. +toyonly testcmd '-nf multiple args' '-n link link' "link\nlink" '' '' +testcmd '-nz' '-nz link' 'link' '' '' +testcmd '-z' '-z link' 'link\0' '' '' mkdir sub && ln -s . here && @@ -51,7 +55,8 @@ "readlink -f /dev/null/file 2>/dev/null || echo yes" "yes\n" "" "" testing "-m missing/dir" "readlink -m sub/two/three" "$APWD/sub/two/three\n" "" "" testing "-m missing/../elsewhere" "readlink -m sub/two/../../three" "$APWD/three\n" "" "" -testing "-m file/dir" "readlink -m sub/bang/two 2>/dev/null || echo err" "err\n" "" "" +# TODO: host bug? That's not missing, that's "cannot exist". +toyonly testing "-m file/dir" "readlink -m sub/bang/two 2>/dev/null || echo err" "err\n" "" "" rm link ln -sf / link || exit 1 testing "-f link->/" "readlink -e link/dev" "/dev\n" "" "" diff -Nru toybox-0.8.8+dfsg/tests/realpath.test toybox-0.8.9+dfsg/tests/realpath.test --- toybox-0.8.8+dfsg/tests/realpath.test 2022-08-12 07:58:03.000000000 +0000 +++ toybox-0.8.9+dfsg/tests/realpath.test 2023-01-10 19:24:45.000000000 +0000 @@ -6,34 +6,50 @@ TOP="$(readlink -f .)" -testcmd '' '.' "$TOP\n" '' '' -#testcmd '-z' '-z . | tr "\0" X' "${TOP}X" '' '' touch file -testcmd 'file' 'file' "$TOP/file\n" '' '' mkdir -p one/two/three +ln -s ./one uno +ln -s one/two dos + +testcmd '' '.' "$TOP\n" '' '' +testcmd 'missing' 'missing' "$TOP/missing\n" '' '' +testcmd 'missing2' 'missing/sub 2>/dev/null || echo err' 'err\n' '' '' +testcmd '-z' '-z . | tr "\0" X' "${TOP}X" '' '' +testcmd 'file' 'file' "$TOP/file\n" '' '' testcmd 'dir' 'one/two/three' "$TOP/one/two/three\n" '' '' -#testcmd '--relative-to' '. --relative-to=one/two/three' '../../..\n' '' '' -#testcmd '--relative-base' 'one one/two one/two/three --relative-base=one/two' \ -# "$TOP/one\n.\nthree\n" '' '' -#testcmd '--relative-base stomps --relative-to' \ -# '--relative-to=.. --relative-base=one/two one' "$TOP/one\n" '' '' +testcmd '--relative-to' '. --relative-to=one/two/three' '../../..\n' '' '' +testcmd '--relative-to2' \ + '-m --relative-to=missing/that/ uno/../dos/linux/../../bingeley/bongeley/beep' \ + '../../one/bingeley/bongeley/beep\n' '' '' +testcmd '--relative-to3' '-m walrus --relative-to walrus' '.\n' '' '' +testcmd '--relative-to4' '"$PWD" --relative-to one' '..\n' '' '' +testcmd '--relative-base' 'one one/two one/two/three --relative-base=one/two' \ + "$TOP/one\n.\nthree\n" '' '' +testcmd '--relative-base stomps --relative-to' \ + '--relative-to=.. --relative-base=one/two one' "$TOP/one\n" '' '' +testcmd '-m with relative-base1' '-m --relative-base wurble wurble/poing' \ + 'poing\n' '' '' +testcmd '-m with relative-base2' '-sm --relative-base wurble .' "$PWD\n" '' '' +testcmd '-m with relative-base3' '-m --relative-base wurble wurble wurble/' \ + '.\n.\n' '' '' testcmd 'missing defaults to -m' 'missing' "$TOP/missing\n" '' '' testcmd 'missing -e' '-e missing 2>/dev/null || echo ok' 'ok\n' '' '' +testcmd '-L' '-L dos/../one' "$TOP/one\n" '' '' # The -s tests use $PWD instead of $TOP because symlinks in path _to_ here # should not be resolved either. The shell exports $PWD: use it. -ln -s ./one uno -#testcmd '-s' '-s uno/two' "$PWD/uno/two\n" '' '' -ln -s one/two dos -#testcmd '-s link/..' '-es dos/three' "$PWD/dos/three\n" '' '' -#testcmd '-s .. eats symlink' '-s dos/..' "$PWD\n" '' '' +testcmd '-s' '-s uno/two' "$PWD/uno/two\n" '' '' +testcmd '-s link/..' '-es dos/three' "$PWD/dos/three\n" '' '' +testcmd '-s .. eats symlink' '-s dos/..' "$PWD\n" '' '' # In toybox this test is consistent with the previous one -#toyonly testing '-s .. eats symlink in $PWD' \ -# 'cd dos && realpath -s ..' "$PWD\n" '' '' +toyonly testing '-s .. eats symlink in $PWD' \ + 'cd dos && realpath -s ..' "$PWD\n" '' '' # Logically -es means the _symlink_ should exist, but match behavior... ln -s missing dangling -#testcmd '-es dangling symlink' '-es dangling 2>/dev/null || echo ok' \ -# 'ok\n' '' '' -#testcmd '-ms' '-ms dangling/../dos/../one/two' "$PWD/one/two\n" '' '' +testcmd '-es dangling symlink' '-es dangling 2>/dev/null || echo ok' \ + 'ok\n' '' '' +testcmd '-ms' '-ms dangling/../dos/../one/two' "$PWD/one/two\n" '' '' ln -s ../two/.. one/two/ichi -#testcmd '-es' '-es one/two/ichi/two/ichi/two' "$PWD/one/two/ichi/two/ichi/two\n" '' '' +testcmd '-es' '-es one/two/ichi/two/ichi/two' "$PWD/one/two/ichi/two/ichi/two\n" '' '' + +rm -rf file one uno dos diff -Nru toybox-0.8.8+dfsg/tests/sed.test toybox-0.8.9+dfsg/tests/sed.test --- toybox-0.8.8+dfsg/tests/sed.test 2022-08-12 07:58:03.000000000 +0000 +++ toybox-0.8.9+dfsg/tests/sed.test 2023-01-10 19:24:45.000000000 +0000 @@ -129,6 +129,9 @@ "one two three\nabcthreedef four five\nfive six seven\n" "" \ "one two three\nthree four five\nfive six seven\n" +testcmd "interleave -e and -f" "-e 'a abc' -f input -e 'a ghi'" \ + "hello\nabc\ndef\nghi\n" "a def" "hello\n" + # Different ways of parsing line continuations testing "" "sed -e '1a\' -e 'huh'" "meep\nhuh\n" "" "meep" @@ -162,11 +165,6 @@ testing "not -s" "sed -n 1p input -" "one" "one" "two" testing "-s" "sed -sn 1p input -" "one\ntwo" "one\n" "two" -#echo meep | sed/sed -e '1a\' -e 'huh' -#echo blah | sed/sed -f <(echo -e "1a\\\\\nboom") -#echo merp | sed/sed "1a\\ -#hello" - testing "bonus backslashes" \ "sed -e 'a \l \x\' -e \"\$(echo -e 'ab\\\nc')\"" \ "hello\nl x\nab\nc\n" "" "hello\n" @@ -175,7 +173,17 @@ testing "end b with }" "sed -n '/START/{:a;n;/END/q;p;ba}'" "b\nc\n" \ "" "a\nSTART\nb\nc\nEND\nd" -testing '-z' 'sed -z "s/\n/-/g"' "a-b-c" "" "a\nb\nc" +testcmd '-z' '-z "s/\n/-/g"' "a-b-c" "" "a\nb\nc" +testcmd '-z N' '-z N' 'one\0two\0' '' 'one\0two\0' +testcmd 'p noeol' '-z p' 'one\0one' '' 'one' +testcmd '-z N noeol' '-z N' 'one\0two' '' 'one\0two' +testcmd '-z S' "-z 'N;P'" 'one\0one\0two' '' 'one\0two' +testcmd '-z D' "-z 'N;D'" 'two' '' 'one\0two' +testcmd '-z G' "-z 'h;G'" 'one\0one' '' 'one' +testcmd '-z H' "-z 'H;g'" '\0one' '' 'one' +toyonly testcmd '-z x NOEOL' '-z ax' 'abc\0x\0def\0x\0' '' 'abc\0def' +testcmd 's after NUL' 's/t/x/' 'one\0xwo' '' 'one\0two' +testcmd '^ not trigger after NUL' 's/^t/x/' 'one\0two' '' 'one\0two' # toybox handling of empty capturing groups broke minjail. Check that we # correctly replace an empty capturing group with the empty string: @@ -200,4 +208,14 @@ testing 's i and I' 'sed s/o/0/ig' "f00l F00L" "" "fool FOOL" +testing 's l ignores posix' "sed -n 'N;l'" 'one\\ntwo$\n' '' 'one\ntwo\n' +testing 's l loses missing newline' "sed -n 'N;l'" 'one\\ntwo$\n' '' 'one\ntwo' +testing 's -z l' "sed -zn 'N;l'" 'one\\000two$\0' '' 'one\0two\0' +testing 's -z l missing newline' "sed -zn 'N;l'" 'one\\000two$\0' '' 'one\0two' + # -i with $ last line test + +#echo meep | sed/sed -e '1a\' -e 'huh' +#echo blah | sed/sed -f <(echo -e "1a\\\\\nboom") +#echo merp | sed/sed "1a\\ +#hello" diff -Nru toybox-0.8.8+dfsg/tests/seq.test toybox-0.8.9+dfsg/tests/seq.test --- toybox-0.8.8+dfsg/tests/seq.test 2022-08-12 07:58:03.000000000 +0000 +++ toybox-0.8.9+dfsg/tests/seq.test 2023-01-10 19:24:45.000000000 +0000 @@ -73,4 +73,6 @@ testing "INT_MIN" "seq -2147483648 -2147483647" "-2147483648\n-2147483647\n"\ "" "" +# macOS doesn't include a timeout(1), nor a good alternative. +skipnot [ "$(uname)" != "Darwin" ] testing "fast path" "timeout 10 seq 10000000 > /dev/null" "" "" "" diff -Nru toybox-0.8.8+dfsg/tests/sh.test toybox-0.8.9+dfsg/tests/sh.test --- toybox-0.8.8+dfsg/tests/sh.test 2022-08-12 07:58:03.000000000 +0000 +++ toybox-0.8.9+dfsg/tests/sh.test 2023-01-10 19:24:45.000000000 +0000 @@ -57,7 +57,7 @@ shxpect "prompt and exit" I$'exit\n' shxpect "prompt and echo" I$'echo hello\n' O$'hello\n' E"$P" -shxpect "redirect err" I$'echo > /dev/full\n' E E"$P" +shxpect "redirect err" I$'echo > /dev/full\n' E E"$P" X1 shxpect "wait for <(exit)" I$'cat <(echo hello 1>&2)\n' E$'hello\n' E"$P" # Test the sh -c stuff before changing EVAL @@ -81,29 +81,9 @@ 'x\n' '' '' # The bash man page is lying when it says $_ starts with an absolute path. -ln -s $(which $SH) bash +ln -s "$(which $SH)" bash testing 'non-absolute $_' "./bash -c 'echo \$_'" './bash\n' '' '' rm bash -testing '$_ with functions' 'true; x(){ echo $_;}; x abc; echo $_' \ - 'true\nabc\n' '' '' - -shxpect '$_ preserved on assignment error' I$'true hello; a=1 b=2 c=${}\n' \ - E E"$P" I$'echo $_\n' O$'hello\n' -shxpect '$_ preserved on prefix error' I$'true hello; a=1 b=2 c=${} true\n' \ - E E"$P" I$'echo $_\n' O$'hello\n' -shxpect '$_ preserved on exec error' I$'true hello; ${}\n' \ - E E"$P" I$'echo $_\n' O$'hello\n' -shxpect '$_ abspath on exec' I$'env | grep ^_=\n' O$'_=/usr/bin/env\n' -testing '$_ literal after exec' 'env >/dev/null; echo $_' 'env\n' '' '' -shxpect '$_ no path for builtin' I$'true; echo $_\n' O$'true\n' -testing 'prefix is local for builtins' 'abc=123; abc=def unset abc; echo $abc' \ - '123\n' '' '' -testing 'prefix localizes magic vars' \ - 'SECONDS=123; SECONDS=345 true; echo $SECONDS' '123\n' '' '' -shxpect 'body evaluated before variable exports' I$'a=x${} y${}\n' RE'y${}' -testing '$NOTHING clears $_' 'true; $NOTHING; echo $_' '\n' '' '' -testing 'assignment with redirect is persistent, not prefix' \ - 'ABC=DEF > potato && rm potato && echo $ABC' 'DEF\n' '' '' testing 'exec exitval' "$SH -c 'exec echo hello' && echo \$?" "hello\n0\n" "" "" testing 'simple script' '$SH input' 'input\n' 'echo $0' '' @@ -133,12 +113,34 @@ # "{ \$SH -c 'X=\${a?blah} > walroid';ls walroid;} 2>/dev/null" '' '' '' testing "lineno" "$SH input" "5 one\n6 one\n5 two\n6 two\n" \ '#!/bin/bash\n\nfor i in one two\ndo\n echo $LINENO $i\n echo $LINENO $i\ndone\n' "" -testing "eval0" "sh -c 'eval echo \$*' one two three" "two three\n" "" "" +testing "eval0" "$SH -c 'eval echo \$*' one two three" "two three\n" "" "" ######################################################################### # Change EVAL to call sh -c for us, using "bash" explicitly for the host. export EVAL="timeout 10 $SH -c" +testing 'trailing $ is literal' 'echo $' '$\n' '' '' +# TODO testing 'empty +() is literal' 'echo +()' '+()\n' '' '' +shxpect 'queued work after HERE' I$'<<0;echo hello\n' E"> " I$'0\n' O$'hello\n' +shxpect '$_ preserved on assignment error' I$'true hello; a=1 b=2 c=${}\n' \ + E E"$P" I$'echo $_\n' O$'hello\n' +shxpect '$_ preserved on prefix error' I$'true hello; a=1 b=2 c=${} true\n' \ + E E"$P" I$'echo $_\n' O$'hello\n' +shxpect '$_ preserved on exec error' I$'true hello; ${}\n' \ + E E"$P" I$'echo $_\n' O$'hello\n' +shxpect '$_ abspath on exec' I$'env | grep ^_=\n' O$'_=/usr/bin/env\n' +testing '$_ literal after exec' 'env >/dev/null; echo $_' 'env\n' '' '' +shxpect '$_ no path for builtin' I$'true; echo $_\n' O$'true\n' +testing 'prefix is local for builtins' 'abc=123; abc=def unset abc; echo $abc' \ + '123\n' '' '' +testing 'prefix localizes magic vars' \ + 'SECONDS=123; SECONDS=345 true; echo $SECONDS' '123\n' '' '' +shxpect 'body evaluated before variable exports' I$'a=x${} y${}\n' RE'y${}' X1 +testing '$NOTHING clears $_' 'true; $NOTHING; echo $_' '\n' '' '' +testing 'assignment with redirect is persistent, not prefix' \ + 'ABC=DEF > potato && rm potato && echo $ABC' 'DEF\n' '' '' +testing '$_ with functions' 'true; x(){ echo $_;}; x abc; echo $_' \ + 'true\nabc\n' '' '' mkdir -p one/two/three testing 'cd in renamed dir' \ @@ -213,6 +215,8 @@ 'ana a\n' '' '' toyonly testing '${x#utf8}' 'x=aそcde; echo ${x##a?c}' 'de\n' '' '' testing '${x%y}' 'x=potato; echo ${x%t*o} ${x%%t*o}' 'pota po\n' '' '' +testing 'x=${x%y}' 'x=potato; x=${x%t*o}; echo $x' 'pota\n' '' '' +testing 'x=${x//y}' 'x=potato; x=${x//ta}; echo $x' 'poto\n' '' '' testing '${x^y}' 'x=aaaaa; echo ${x^a}' 'Aaaaa\n' '' '' testing '${x^^y}' 'x=abccdec; echo ${x^^c}; x=abcdec; echo ${x^^c}' \ 'abCCdeC\nabCdeC\n' '' '' @@ -231,7 +235,7 @@ testing "backtick1" 'x=fred; echo `echo $x`' 'fred\n' "" "" testing "backtick2" 'x=fred; echo `x=y; echo $x`; echo $x' 'y\nfred\n' "" "" testing '$(( ) )' 'echo ab$((echo hello) | tr e x)cd' "abhxllocd\n" "" "" -SKIPNEXT=1 testing '$((x=y)) lifetime' 'a=boing; echo $a $a$((a=4))$a $a' 'boing boing44 4\n' '' '' +((++SKIP)); testing '$((x=y)) lifetime' 'a=boing; echo $a $a$((a=4))$a $a' 'boing boing44 4\n' '' '' testing 'quote' "echo \"'\"" "'\n" "" "" @@ -253,6 +257,19 @@ testing "nesting ? :" \ 'for((i=0;i<8;i++)); do echo $((i&1?i&2?1:i&4?2:3:4));done' \ '4\n3\n4\n1\n4\n2\n4\n1\n' '' '' +testing "inherited assignment suppression" 'echo $((0 ? (x++) : 2)); echo $x' \ + "2\n\n" "" "" +testing "boolean vs logical" 'echo $((2|4&&8))' '1\n' '' '' +testing "&& vs || priority" \ + 'echo $((w++||x++&&y++||z++)) w=$w x=$x y=$y z=$z' \ + '0 w=1 x=1 y= z=1\n' '' '' +testing "|| vs && priority" \ + 'echo $((w++&&x++||y++&&z++)) w=$w x=$x y=$y z=$z' \ + '0 w=1 x= y=1 z=\n' '' '' +shxpect '/0' I$'echo $((1/0)); echo here\n' E E"$P" I$'echo $?\n' O$'1\n' +shxpect '%0' I$'echo $((1%0)); echo here\n' E E"$P" I$'echo $?\n' O$'1\n' +shxpect '/=0' I$'echo $((x/=0)); echo here\n' E E"$P" I$'echo $?\n' O$'1\n' +shxpect '%=0' I$'echo $((x%=0)); echo here\n' E E"$P" I$'echo $?\n' O$'1\n' # Loops and flow control testing "case" 'for i in A C J B; do case "$i" in A) echo got A ;; B) echo and B ;; C) echo then C ;; *) echo default ;; esac; done' \ @@ -338,6 +355,8 @@ testing "IFS4" 'IFS=x; echo $(echo abx)y' "ab y\n" "" "" testing "IFS5" 'IFS=xy; for i in abcxdefyghi; do echo =$i=; done' \ "=abc def ghi=\n" "" "" +testing "curly bracket whitespace" 'for i in {$,} ""{$,}; do echo ="$i"=; done'\ + '=$=\n=$=\n==\n' '' '' testing 'empty $! is blank' 'echo $!' "\n" "" "" testing '$! = jobs -p' 'true & [ $(jobs -p) = $! ] && echo yes' "yes\n" "" "" @@ -597,7 +616,7 @@ 'func() { eval "echo one; echo \${?potato}; echo and" 2>/dev/null; echo plus;}; func; echo then' \ 'one\nplus\nthen\n' '' '' -shxpect "functions need block" I$'x() echo;\n' RE'[Ss]yntax [Ee]rror' +shxpect "functions need block" I$'x() echo;\n' RE'[Ss]yntax [Ee]rror' X2 testing 'functions() {} in same PID' \ '{ echo $BASHPID; chicken() { echo $BASHPID;}; chicken;} | sort -u | wc -l' '1\n' '' '' testing 'functions() () different PID' \ diff -Nru toybox-0.8.8+dfsg/tests/sort.test toybox-0.8.9+dfsg/tests/sort.test --- toybox-0.8.8+dfsg/tests/sort.test 2022-08-12 07:58:03.000000000 +0000 +++ toybox-0.8.9+dfsg/tests/sort.test 2023-01-10 19:24:45.000000000 +0000 @@ -104,6 +104,11 @@ # "toy-2.37.tar.gz\ntoy-3.4.tar.gz\ntoy-3.12.tar.gz\ntoy-4.16-rc2.tar.gz\ntoy-4.16.tar.gz\n" "" \ # "toy-3.12.tar.gz\ntoy-2.37.tar.gz\ntoy-3.4.tar.gz\ntoy-4.16-rc2.tar.gz\ntoy-4.16.tar.gz" +testcmd "-c" "-c 2>&1 | grep -o [0-9]*" "3\n" "" "a\nb\na\nc" +testcmd "-uc" "-uc 2>&1 | grep -o [0-9]*" "3\n" "" "a\nb\nb\nc" +testcmd "-C 1" "-C || echo yes" "yes\n" "" "one\ntwo\nthree" +testcmd "-C 2" "-C && echo yes" "yes\n" "" "a\nb\nc\n" + optional SORT_FLOAT # not numbers < NaN < -infinity < numbers < +infinity diff -Nru toybox-0.8.8+dfsg/tests/tar.test toybox-0.8.9+dfsg/tests/tar.test --- toybox-0.8.8+dfsg/tests/tar.test 2022-08-12 07:58:03.000000000 +0000 +++ toybox-0.8.9+dfsg/tests/tar.test 2023-01-10 19:24:45.000000000 +0000 @@ -4,156 +4,161 @@ #testing "name" "command" "result" "infile" "stdin" -# For reproducibility: UTC and umask 0002 - -OLDTZ="$TZ" +# For reproducibility: TZ=UTC, umask 0002, override ownership and timestamp export TZ=utc -OLDUMASK=$(umask) umask 0002 +TAR='tar c --owner root --group sys --mtime @1234567890' # 255 bytes, longest VFS name LONG=0123456789abcdef0123456789abcdef LONG=$LONG$LONG$LONG$LONG$LONG$LONG$LONG$LONG LONG=${LONG:1:255} -# Reproducible tarballs: override ownership and timestamp. -TAR='tar c --owner root --group root --mtime @1234567890' +# We check both sha1sum (to ensure binary identical output) and list contents. -# Different tars add variable trailing NUL padding (1024 bytes is just minimum) -# so look at first N 512-byte frames when analyzing header content. +# Check hash of first N 512 byte frames to ensure result is binary identical. function SUM() { + # Different tars add variable trailing NUL padding (1024 bytes is just + # minimum) so look at first N 512-byte frames when analyzing header content. tee save.dat | head -c $(($1*512)) | sha1sum | sed "s/ .*//" } +# List tarball contents, converting variable tabs into one space function LST() { tar tv "$@" | sed 's/[ \t][ \t]*/ /g' } +# Check that stored empty file is binary identical and decodes as expected. touch file -testing "create file" "$TAR file | SUM 3" \ - "fecaecba936e604bb115627a6ef4db7c7a3a8f81\n" "" "" - +testing "store file" "$TAR file | SUM 3" \ + "2735f3a18d770dd0d7145d76108532f72bef9927\n" "" "" testing "pass file" "$TAR file | LST" \ - "-rw-rw-r-- root/root 0 2009-02-13 23:31 file\n" "" "" + "-rw-rw-r-- root/sys 0 2009-02-13 23:31 file\n" "" "" +# Two files from -T list touch file1 file2 -echo -e "file1\nfile2" > files-newline -testing "-T newline" "$TAR -T files-newline | LST" \ - "-rw-rw-r-- root/root 0 2009-02-13 23:31 file1\n-rw-rw-r-- root/root 0 2009-02-13 23:31 file2\n" "" "" -tr '\n' '\0' < files-newline > files-null -testing "-T null" "$TAR --null -T files-null | LST" \ - "-rw-rw-r-- root/root 0 2009-02-13 23:31 file1\n-rw-rw-r-- root/root 0 2009-02-13 23:31 file2\n" "" "" - -# The kernel has two hardwired meaningful UIDs: 0 (root) and 65534 (nobody). -# (Technically changeable via /proc/sys/*/overflowuid but nobody ever does) -skipnot id nobody >/dev/null -testing "pass user" "tar -c --owner nobody:65534 --group root --mtime @0 file | LST" \ - "-rw-rw-r-- nobody/root 0 1970-01-01 00:00 file\n" "" "" -# (We assume that if we have the nobody user, we also have the group, in the -# absence of a good portable way to test for the existence of a named group.) -skipnot id nobody >/dev/null -testing "pass group" "tar c --owner root --group nobody:65534 --mtime @0 file | LST" \ +testing "-T newline" "$TAR -T input | LST" \ + "-rw-rw-r-- root/sys 0 2009-02-13 23:31 file1\n-rw-rw-r-- root/sys 0 2009-02-13 23:31 file2\n" "file1\nfile2\n" "" +testing "-T null" "$TAR --null -T input | LST" \ + "-rw-rw-r-- root/sys 0 2009-02-13 23:31 file1\n-rw-rw-r-- root/sys 0 2009-02-13 23:31 file2\n" "file1\0file2\0" "" + +# User "root" is UID 0 and group "sys" is GID 3 (on Linux, BSD, and Mac), +# inherited from Bell Labs Unix v7 + +# Note: testing both "tar c" and "tar -c" here. +testing "specify UID, fetch GID" "tar -c --owner nobody:65534 --group sys --mtime @0 file | LST" \ + "-rw-rw-r-- nobody/sys 0 1970-01-01 00:00 file\n" "" "" +testing "fetch UID, specify GID" "tar c --owner root --group nobody:65534 --mtime @0 file | LST" \ "-rw-rw-r-- root/nobody 0 1970-01-01 00:00 file\n" "" "" -# Historically we output a "base 256" format that _we_ could decode but that -# GNU tar choked on, so check the exact bytes with SUM, not a LST round trip. +# Large values switch from ascii numbers to a binary format. testing "huge values" "tar c --owner 9999999 --group 8888888 --mtime @0 file | SUM 3" \ "396b07fd2f80eeb312462e3bfb7dc1325dc6bcfb\n" "" "" +testcmd "longname" "tf $FILES/tar/long_path.tar" \ + "$(printf 'long file name%86cTRAILING' ' ' | tr ' ' _)\n" "" "" + touch -t 198701231234.56 file testing "pass mtime" \ - "tar c --owner root --group root file | LST --full-time" \ - "-rw-rw-r-- root/root 0 1987-01-23 12:34:56 file\n" "" "" + "tar c --owner root --group sys file | LST --full-time" \ + "-rw-rw-r-- root/sys 0 1987-01-23 12:34:56 file\n" "" "" testing "adjust mode" \ - "tar c --owner root --group root --mode a+x file | LST --full-time" \ - "-rwxrwxr-x root/root 0 1987-01-23 12:34:56 file\n" "" "" + "tar c --owner root --group sys --mode a+x file | LST --full-time" \ + "-rwxrwxr-x root/sys 0 1987-01-23 12:34:56 file\n" "" "" mkdir dir -testing "create dir" "$TAR dir | SUM 3" \ - "05739c423d7d4a7f12b3dbb7c94149acb2bb4f8d\n" "" "" - +testing "store dir" "$TAR dir | SUM 3" \ + "85add1060cfe831ca0cdc945158efe6db485b81e\n" "" "" testing "pass dir" "$TAR dir | LST" \ - "drwxrwxr-x root/root 0 2009-02-13 23:31 dir/\n" "" "" + "drwxrwxr-x root/sys 0 2009-02-13 23:31 dir/\n" "" "" # note: does _not_ include dir entry in archive, just file touch dir/file -testing "create file in dir" "$TAR dir/file | SUM 3" \ - "2d7b96c7025987215f5a41f10eaa84311160afdb\n" "" "" +testing "store file in dir" "$TAR dir/file | SUM 3" \ + "d9e7fb3884430d29e7eed0dc04a2593dd260df14\n" "" "" -# Tests recursion without worrying about content order -testing "create dir and dir/file" "$TAR dir | SUM 3" \ - "0bcc8005a3e07eb63c9b735267aecc5b774795d7\n" "" "" +# Test recursion with one file so filesystem sort order can't change result +testing "store dir and dir/file" "$TAR dir | SUM 3" \ + "a4e35f87e28c4565b60ba01dbe79e431914f8788\n" "" "" testing "pass dir/file" "$TAR dir | LST" \ - "drwxrwxr-x root/root 0 2009-02-13 23:31 dir/\n-rw-rw-r-- root/root 0 2009-02-13 23:31 dir/file\n" "" "" + "drwxrwxr-x root/sys 0 2009-02-13 23:31 dir/\n-rw-rw-r-- root/sys 0 2009-02-13 23:31 dir/file\n" "" "" echo boing > dir/that testing "tar C" "$TAR -C dir that | SUM 3" \ - "f0deff71bf4858eb0c5f49d99d052f12f1831feb\n" "" "" + "d469d4bc06def2d8808400ba30025ca295d05e4f\n" "" "" -# / and .. only stripped from name, not symlink target. -ln -s ../name.././.. dir/link -testing "create symlink" "$TAR dir/link | SUM 3" \ - "7324cafbd9aeec5036b6efc54d741f11528aeb10\n" "" "" - -# Also two explicit targets ln dir/file dir/hardlink -testing "create hardlink" "$TAR dir/file dir/hardlink | SUM 3" \ - "c5383651f8c03ec0fe15e8a9e28a4e8e5273990d\n" "" "" - -ln dir/link dir/hlink -testing "create hardlink to symlink" "$TAR dir/link dir/hlink | SUM 3" \ - "3bc16f8fb6fc8b05f691da8caf989a70ee99284a\n" "" "" +testing "store hardlink" "$TAR dir/file dir/hardlink | SUM 3" \ + "519de8abd1b32debd495a0fc1d96082184abbdcc\n" "" "" skipnot mkfifo dir/fifo 2>/dev/null testing "create dir/fifo" "$TAR dir/fifo | SUM 3" \ - "bd1365db6e8ead4c813333f9666994c1899924d9\n" "" "" + "cad477bd0fc5173d0a43f4774f514035456960e6\n" "" "" # test L and K records # 4+96=100 (biggest short name), 4+97=101 (shortest long name) touch dir/${LONG:1:96} dir/${LONG:1:97} testing "create long fname" "$TAR dir/${LONG:1:97} dir/${LONG:1:96} | SUM 3" \ - "99348686fe9c9bf80f5740f1fc0c6f32f2021e3d\n" "" "" + "d70018505fa5df19ae73498cfc74d0281601e42e\n" "" "" + +# MacOS X has different symlink permissions, skip these tests there +[ "$(uname)" == Darwin ] && SKIP=999 -ln -s dir/${LONG:1:96} dir/lshort -ln -s dir/${LONG:1:97} dir/llong -testing "create long symlnk" "$TAR dir/lshort dir/llong | SUM 3" \ - "8a5d652dc85f252a2e3b3f47d1ecd699e98a5f4b\n" "" "" - -ln -s $LONG dir/${LONG:5} -testing "create long->long" "$TAR dir/${LONG:5} | SUM 7" \ - "543116b8e690a116a559ab5b673f9b6d6601c925\n" "" "" -# absolute and relative link names, broken and not - -ln -s file dir/linkok -testing "create symlink" "$TAR dir/linkok | SUM 3" \ - "55652846506cf0a9d43b3ef03ccf9e98123befaf\n" "" "" + # / and .. only stripped from name, not symlink target. + ln -s ../name.././.. dir/link + testing "create symlink" "$TAR dir/link | SUM 3" \ + "f841bf9d757c655c5d37f30be62acb7ae24f433c\n" "" "" + + ln dir/link dir/hlink + testing "create hardlink to symlink" "$TAR dir/link dir/hlink | SUM 3" \ + "de571a6dbf09e1485e513ad13a178b1729267452\n" "" "" + + ln -s dir/${LONG:1:96} dir/lshort + ln -s dir/${LONG:1:97} dir/llong + testing "create long symlink" "$TAR dir/lshort dir/llong | SUM 3" \ + "07eaf397634b5443dbf2d3ec38a4302150fcfe82\n" "" "" + + ln -s $LONG dir/${LONG:5} + testing "create long->long" "$TAR dir/${LONG:5} | SUM 7" \ + "b9e24f53e27496c5125445230d201b4a36ff7398\n" "" "" + + # absolute and relative link names, broken and not + ln -s file dir/linkok + testing "create symlink" "$TAR dir/linkok | SUM 3" \ + "f5669cfd179ddcdd5ca9f8a1561a99e11e0a08b1\n" "" "" + +SKIP=0 # End of tests that don't match MacOS symlink permissions + +symlink_perms=lrwxrwxrwx +[ "$(uname)" == "Darwin" ] && symlink_perms=lrwxrwxr-x ln -s /dev/null dir/linknull testing "pass absolute symlink" "$TAR dir/linknull | LST" \ - "lrwxrwxrwx root/root 0 2009-02-13 23:31 dir/linknull -> /dev/null\n" "" "" + "$symlink_perms root/sys 0 2009-02-13 23:31 dir/linknull -> /dev/null\n" "" "" ln -s rel/broken dir/relbrok testing "pass broken symlink" "$TAR dir/relbrok | LST" \ - "lrwxrwxrwx root/root 0 2009-02-13 23:31 dir/relbrok -> rel/broken\n" "" "" + "$symlink_perms root/sys 0 2009-02-13 23:31 dir/relbrok -> rel/broken\n" "" "" ln -s /does/not/exist dir/linkabsbrok testing "pass broken absolute symlink" "$TAR dir/linkabsbrok | LST" \ - "lrwxrwxrwx root/root 0 2009-02-13 23:31 dir/linkabsbrok -> /does/not/exist\n" \ + "$symlink_perms root/sys 0 2009-02-13 23:31 dir/linkabsbrok -> /does/not/exist\n" \ "" "" -# this expects devtmpfs values +nulldev=1,3 # devtmpfs values +[ "$(uname)" == "Darwin" ] && nulldev=3,2 testing "pass /dev/null" \ - "tar c --mtime @0 /dev/null 2>/dev/null | LST" \ - "crw-rw-rw- root/root 1,3 1970-01-01 00:00 dev/null\n" "" "" + "tar c --mtime @0 --group sys /dev/null 2>/dev/null | LST" \ + "crw-rw-rw- root/sys $nulldev 1970-01-01 00:00 dev/null\n" "" "" testing "--absolute-names" \ - "tar c --mtime @0 --absolute-names /dev/null 2>/dev/null | LST" \ - "crw-rw-rw- root/root 1,3 1970-01-01 00:00 /dev/null\n" "" "" + "tar c --mtime @0 --group sys --absolute-names /dev/null 2>/dev/null | LST" \ + "crw-rw-rw- root/sys $nulldev 1970-01-01 00:00 /dev/null\n" "" "" # compression types testing "autodetect gzip" 'LST -f "$FILES"/tar/tar.tgz' \ @@ -171,22 +176,24 @@ "drwxr-x--- enh/eng 0 2017-05-13 01:05 dir/\n-rw-r----- enh/eng 12 2017-05-13 01:05 dir/file\n" \ "" "" -skipnot mknod dir/char c 12 34 2>/dev/null -testing "character special" "tar --mtime @0 -cf test.tar dir/char && rm -f dir/char && tar xf test.tar && ls -l dir/char" \ - "crw-rw---- 1 root root 12, 34 1970-01-01 00:00 dir/char\n" "" "" - -skipnot mknod dir/block b 23 45 2>/dev/null -testing "block special" "tar --mtime @0 -cf test.tar dir/block && rm -f dir/block && tar xf test.tar && ls -l dir/block" \ - "brw-rw---- 1 root root 23, 45 1970-01-01 00:00 dir/block\n" "" "" +skipnot mknod -m 660 dir/char c 12 34 2>/dev/null && chgrp sys dir/char +NOSPACE=1 testing "character special" "tar --mtime @0 -cf test.tar dir/char && rm -f dir/char && tar xf test.tar && ls -l --full-time dir/char" \ + "crw-rw---- 1 root sys 12, 34 1970-01-01 00:00:00.000000000 +0000 dir/char\n"\ + "" "" -skipnot chown nobody dir/file 2>/dev/null +skipnot mknod -m 660 dir/block b 23 45 2>/dev/null && chgrp sys dir/block +NOSPACE=1 testing "block special" "tar --mtime @0 -cf test.tar dir/block && rm -f dir/block && tar xf test.tar && ls -l --full-time dir/block" \ + "brw-rw---- 1 root sys 23, 45 1970-01-01 00:00:00.000000000 +0000 dir/block\n"\ + "" "" + +skipnot chown nobody:nogroup dir/file 2>/dev/null testing "ownership" "$TAR dir/file | SUM 3" \ "2d7b96c7025987215f5a41f10eaa84311160afdb\n" "" "" mkdir -p dd/sub/blah && tar cf test.tar dd/sub/blah && rm -rf dd/sub && -ln -s ../.. dd/sub || SKIPNEXT=1 +skipnot ln -s ../.. dd/sub toyonly testing "symlink out of cwd" \ "tar xf test.tar 2> /dev/null || echo yes ; [ ! -e dd/sub/blah ] && echo yes" \ "yes\nyes\n" "" "" @@ -218,48 +225,59 @@ "dir/\ndir/file\ndrwxr-x--- 1494637555 dd/dir\n-rw-r----- 1494637555 dd/dir/file\n" \ "" "" -yes | head -n $((1<<18)) > bang -{ dd bs=$((1<<16)) count=1 status=none; dd bs=8192 seek=14 count=1 status=none; dd bs=4096 seek=64 count=5 status=none; } < bang > fweep -testing "sparse without overflow" "$TAR --sparse fweep | SUM 3" \ - "e1560110293247934493626d564c8f03c357cec5\n" "" "" -rm bang fweep - -for i in 1 3 5 7 9 14 27 36 128 256 300 304 -do - dd if=/dev/zero of=fweep bs=65536 seek=$i count=1 2>/dev/null -done - -testing "sparse single overflow" "$TAR --sparse fweep | SUM 6" \ - "063fc6519ea2607763bc591cc90dd15ac2b43eb8\n" "" "" - -rm fweep -for i in $(seq 8 3 200) -do - dd if=/dev/zero of=fweep bs=65536 seek=$i count=1 2>/dev/null - dd if=/dev/zero of=fweep2 bs=65536 seek=$i count=1 2>/dev/null -done -truncate -s 20m fweep2 - -testing "sparse double overflow" "$TAR --sparse fweep | SUM 7" \ - "f1fe57f8313a9d682ec9013a80f3798910b6ff51\n" "" "" - -tar c --sparse fweep > fweep.tar -rm fweep -testing "sparse extract" "tar xf fweep.tar && $TAR --sparse fweep | SUM 4" \ - "38dc57b8b95632a287db843c214b5c96d1cfe415\n" "" "" -testing "sparse tvf" "tar tvf fweep.tar | grep -wq 13172736 && echo right size"\ - "right size\n" "" "" -rm fweep fweep.tar - -tar c --sparse fweep2 > fweep2.tar -rm fweep2 -testing "sparse extract hole at end" \ - "tar xf fweep2.tar && $TAR --sparse fweep2 | SUM 4" \ - "791060574c569e5c059e2b90c1961a3575898f97\n" "" "" -rm fweep2 fweep2.tar +mkdir path && ln -s "$(which gzip)" "$(which tar)" path/ && [ -x path/gzip ] || + ((++SKIP)) +toyonly testing "autodetect falls back to gzip -d when no zcat" \ + "PATH=path; tar tf $FILES/tar/tar.tgz" "dir/\ndir/file\n" "" "" +rm -rf path + +# Tests that don't produce the same results on MacOS X as Linux +[ "$(uname)" == Darwin ] && SKIP=999 + + yes | head -n $((1<<18)) > bang + { + dd bs=$((1<<16)) count=1 status=none + dd bs=8192 seek=14 count=1 status=none + dd bs=4096 seek=64 count=5 status=none + } < bang > fweep + testing "sparse without overflow" "$TAR --sparse fweep | SUM 3" \ + "50dc56c3c7eed163f0f37c0cfc2562852a612ad0\n" "" "" + rm bang fweep + + for i in 1 3 5 7 9 14 27 36 128 256 300 304 + do + dd if=/dev/zero of=fweep bs=65536 seek=$i count=1 2>/dev/null + done + testing "sparse single overflow" "$TAR --sparse fweep | SUM 6" \ + "81d59c3a7470201f92d60e63a43318ddde893f6d\n" "" "" + rm fweep + + for i in $(seq 8 3 200) + do + dd if=/dev/zero of=fweep bs=65536 seek=$i count=1 2>/dev/null + dd if=/dev/zero of=fweep2 bs=65536 seek=$i count=1 2>/dev/null + done + truncate -s 20m fweep2 + testing "sparse double overflow" "$TAR --sparse fweep | SUM 7" \ + "024aacd955e45f89bafedb3f37c8d39b4d556471\n" "" "" + + tar c --sparse fweep > fweep.tar + rm fweep + testing "sparse extract" "tar xf fweep.tar && $TAR --sparse fweep | SUM 4" \ + "b949d3a3b4c6457c873f1ea9918fd9029c5ed4b3\n" "" "" + testing "sparse tvf" \ + "tar tvf fweep.tar | grep -wq 13172736 && echo right size" "right size\n" \ + "" "" + rm fweep fweep.tar + + tar c --sparse fweep2 > fweep2.tar + rm fweep2 + testing "sparse extract hole at end" \ + "tar xf fweep2.tar && $TAR --sparse fweep2 | SUM 4" \ + "807664bcad0e827793318ff742991d6f006b2127\n" "" "" + rm fweep2 fweep2.tar -testcmd "longname" "tf $FILES/tar/long_path.tar" \ - "$(printf 'long file name%86cTRAILING' ' ' | tr ' ' _)\n" "" "" +SKIP=0 # End of tests that don't work on MacOS X mkdir -p links touch links/orig @@ -286,20 +304,83 @@ mkdir ..dotsdir testing "create ..dotsdir" "$TAR ..dotsdir | SUM 3" \ - "de99091a91c74ef6b90093e9165b413670730572\n" "" "" + "62ff23c9b427020331992b9bc71f082099c1411f\n" "" "" testing "pass ..dotsdir" "$TAR ..dotsdir | LST" \ - "drwxrwxr-x root/root 0 2009-02-13 23:31 ..dotsdir/\n" "" "" + "drwxrwxr-x root/sys 0 2009-02-13 23:31 ..dotsdir/\n" "" "" rmdir ..dotsdir mkdir -p one/two/three/four/five touch one/two/three/four/five/six testing "--strip" "$TAR one | tar t --strip=2 --show-transformed | grep six" \ "three/four/five/six\n" "" "" -testing "--xform" "$TAR one --xform=s@three/four/@zero@ | tar t | grep six" \ + +# toybox tar --xform depends on toybox sed +[ -z "$TEST_HOST" ] && ! sed --tarxform '' /dev/null && SKIP=99 + +mkdir uno +ln -s tres uno/dos +touch uno/tres +ln uno/tres uno/quatro +tt() { $TAR --no-recursion uno uno/{dos,tres,quatro} "$@" | \ + LST --show-transformed-names $XX | sed 's/^.* 23:31 //'; } +testing 'xform S' \ + "tt --xform 's/uno/one/S;s/dos/two/S;s/tres/three/S;s/quatro/four/S'" \ + "one/\none/two -> tres\none/three\none/four link to one/three\n" "" "" + +testing 'xform flags=rh starts with all disabled' \ + "tt --xform 's/uno/one/;flags=rh;s/dos/two/;s/tres/three/;s/quatro/four/'" \ + "one/\none/two -> tres\none/three\none/four link to one/three\n" "" "" + +testing 'xform flags=rHhsS toggles' \ + "tt --xform 's/uno/one/;flags=rHhsS;s/dos/two/;s/tres/three/;s/quatro/four/'"\ + "one/\none/two -> tres\none/three\none/four link to one/three\n" "" "" + +testing 'xform flags= is not a delta from previous' \ + "tt --xform 'flags=s;flags=rh;s/uno/one/;s/dos/two/;s/tres/three/;s/quatro/four/'" \ + "one/\none/two -> tres\none/three\none/four link to one/three\n" "" "" + +testing 'xform H' \ + "tt --xform 'flags=rsH;s/uno/one/;s/dos/two/;s/tres/three/;s/quatro/four/'" \ + "one/\none/two -> three\none/three\none/four link to uno/tres\n" "" "" + +testing 'xform R' \ + "tt --xform 'flags=rshR;s/uno/one/;s/dos/two/;s/tres/three/;s/quatro/four/'" \ + "uno/\nuno/dos -> three\nuno/tres\nuno/quatro link to one/three\n" "" "" + +testing "xform path" "$TAR one --xform=s@three/four/@zero@ | tar t | grep six" \ "one/two/zerofive/six\n" "" "" + +testing "xform trailing slash special case" \ + "$TAR --xform 's#^.+/##x' one/two/three/four/five | tar t" 'five/\nsix\n' '' '' + +# The quoting works because default IFS splits on whitepace not ; +testing "xform extract all" \ + "XX='--xform s/uno/one/;s/dos/two/;s/tres/three/;s/quatro/four/' tt" \ + 'one/\none/two -> three\none/three\none/four link to one/three\n' '' '' + +testing 'xform extract S' \ + "XX='--xform s/uno/one/S;s/dos/two/S;s/tres/three/S;s/quatro/four/S' tt" \ + "one/\none/two -> tres\none/three\none/four link to one/three\n" "" "" + +testing 'xform extract H' \ + "XX='--xform flags=rs;s/uno/one/;s/dos/two/;s/tres/three/;s/quatro/four/' tt"\ + "one/\none/two -> three\none/three\none/four link to uno/tres\n" "" "" + +testing 'xform extract R' \ + "XX='--xform flags=sh;s/uno/one/;s/dos/two/;s/tres/three/;s/quatro/four/' tt"\ + "uno/\nuno/dos -> three\nuno/tres\nuno/quatro link to one/three\n" "" "" + +rm -rf uno +SKIP=0 rm -rf one +testing '-P' "$TAR -P --no-recursion -C / /// .. | SUM 3" \ + "a3e94211582da121845d823374d8f41ead62d7bd\n" "" "" + +testing 'without -P' "$TAR --no-recursion -C / /// .. 2>/dev/null | SUM 3" \ + "077d03243e247b074806904885e6da272fd5857a\n" "" "" + if false then # Sequencing issues that leak implementation details out the interface @@ -341,8 +422,4 @@ fi -TZ="$OLDTZ" -umask $OLDUMASK -unset LONG TAR SUM OLDUMASK OLDTZ -unset -f LST -rm save.dat +rm -f save.dat diff -Nru toybox-0.8.8+dfsg/tests/xxd.test toybox-0.8.9+dfsg/tests/xxd.test --- toybox-0.8.8+dfsg/tests/xxd.test 2022-08-12 07:58:03.000000000 +0000 +++ toybox-0.8.9+dfsg/tests/xxd.test 2023-01-10 19:24:45.000000000 +0000 @@ -9,49 +9,56 @@ # Note that the xxd in vim-common on Ubuntu 14 uses %07x for the file offset. -testing "file1" "xxd file1" \ +testcmd "file1" "file1" \ "00000000: 7468 6973 2069 7320 736f 6d65 2074 6578 this is some tex\n00000010: 740a t.\n" \ "" "" -testing "file1 -l" "xxd -l 2 file1" \ +testcmd "file1 -l" "-l 2 file1" \ "00000000: 7468 th\n" \ "" "" -testing "-" "xxd -" \ +testcmd "-" "-" \ "00000000: 6865 6c6c 6f hello\n" "" "hello" -testing "xxd" "xxd" \ +testcmd "no args" "" \ "00000000: 776f 726c 64 world\n" "" "world" -testing "-c 8 -g 4 file1" "xxd -c 8 -g 4 file1" \ +testcmd "-c 8 -g 4 file1" "-c 8 -g 4 file1" \ "00000000: 74686973 20697320 this is \n00000008: 736f6d65 20746578 some tex\n00000010: 740a t.\n" "" "" -testing "-c 8 -g 3 file1" "xxd -c 8 -g 3 file1" \ +testcmd "-c 8 -g 3 file1" "-c 8 -g 3 file1" \ "00000000: 746869 732069 7320 this is \n00000008: 736f6d 652074 6578 some tex\n00000010: 740a t.\n" "" "" -testing "-i" "cat file1 | xxd -i -" " 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x73, 0x6f, 0x6d, 0x65,\n 0x20, 0x74, 0x65, 0x78, 0x74, 0x0a\n" "" "" +testcmd "-i" "-i - < file1" " 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x73, 0x6f, 0x6d, 0x65,\n 0x20, 0x74, 0x65, 0x78, 0x74, 0x0a\n" "" "" -testing "-o 0x8000" "xxd -o 0x8000 file1" "00008000: 7468 6973 2069 7320 736f 6d65 2074 6578 this is some tex\n00008010: 740a t.\n" "" "" +testcmd "-o 0x8000" "-o 0x8000 file1" "00008000: 7468 6973 2069 7320 736f 6d65 2074 6578 this is some tex\n00008010: 740a t.\n" "" "" -testing "-p" "xxd -p file1" "7468697320697320736f6d6520746578740a\n" "" "" +testcmd "-p" "-p file1" "7468697320697320736f6d6520746578740a\n" "" "" -testing "-s" "xxd -s 13 file1" "0000000d: 7465 7874 0a text.\n" "" "" +# TODO: remove toyonly when distro catches up +toyonly testcmd "-pc0" "-pc0" \ + "73686f77203830206865782064696769747320776974686f757420776f72647772617070696e670a\n" \ + "" "show 80 hex digits without wordwrapping\n" +toyonly testcmd "-pc0 long" "-pc0 | wc -c" "97787\n" "" "$(seq 1 10000)" -testing "-r" "echo -e ' 00000000: 7468 6973 2069 7320 736f 6d65 2074 6578 this is some tex\n00000010: 740a t.' | xxd -r" "this is some text\n" "" "" -toyonly testing "-r -i" "echo -e '0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x73, 0x6f, 0x6d, 0x65,\n 0x20, 0x74, 0x65, 0x78, 0x74, 0x0a' | xxd -ri" "this is some text\n" "" "" -testing "-r -p" "echo 7468697320697320736f6d6520746578740a | xxd -r -p" "this is some text\n" "" "" +testcmd "-s" "-s 13 file1" \ + "0000000d: 7465 7874 0a text.\n" "" "" -testing "-r garbage" "echo '0000: 68 65 6c6c 6fxxxx' | xxd -r -" "hello" "" "" +testcmd "-r" "-r" "this is some text\n" "" \ + ' 00000000: 7468 6973 2069 7320 736f 6d65 2074 6578 this is some tex\n00000010: 740a t.\n' + +toyonly testcmd "-r -i" "-ri" "this is some text\n" "" \ + '0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x73, 0x6f, 0x6d, 0x65,\n 0x20, 0x74, 0x65, 0x78, 0x74, 0x0a\n' + +testcmd "-r garbage" '-r -' 'hello' '' '0000: 68 65 6c6c 6fxxxx\n' # -r will only read -c bytes (default 16) before skipping to the next line, # ignoring the rest. -testing "-r long" \ - "echo '0000: 40404040404040404040404040404040404040404040404040404040404040404040404040404040' | xxd -r -" \ - "@@@@@@@@@@@@@@@@" "" "" +testcmd "-r long" '-r -' "@@@@@@@@@@@@@@@@" "" \ + '0000: 40404040404040404040404040404040404040404040404040404040404040404040404040404040\r' # -r -p ignores the usual -p 30-byte/line limit (or any limit set by -c) and # will take as many bytes as you give it. -testing "-r -p long" \ - "echo '40404040404040404040404040404040404040404040404040404040404040404040404040404040' | xxd -r -p -" \ - "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@" "" "" - -testing "-r unnecessary output seeks" \ - "echo '00000000: 0100 0000 0000 0000 0000 0000 0000 00ff deadbeef........' | xxd -r | xxd" \ - "00000000: 0100 0000 0000 0000 0000 0000 0000 00ff ................\n" "" "" +testcmd "-r -p long" '-r -p -' "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@" "" \ + '40404040404040404040404040404040404040404040404040404040404040404040404040404040\n' + +testcmd "-r unnecessary output seeks" '-r | xxd' \ + "00000000: 0100 0000 0000 0000 0000 0000 0000 00ff ................\n" '' \ + '00000000: 0100 0000 0000 0000 0000 0000 0000 00ff deadbeef........\n' rm file1 file2 diff -Nru toybox-0.8.8+dfsg/tests/zcat.test toybox-0.8.9+dfsg/tests/zcat.test --- toybox-0.8.8+dfsg/tests/zcat.test 2022-08-12 07:58:03.000000000 +0000 +++ toybox-0.8.9+dfsg/tests/zcat.test 2023-01-10 19:24:45.000000000 +0000 @@ -4,17 +4,17 @@ #testing "name" "command" "result" "infile" "stdin" -echo -n "foo " | gzip > f1.gz -echo "bar" | gzip > f2.gz +echo -n "hi " | gzip > 1.gz +echo "there" | gzip > 2.gz -# zcat is basically just `gzip -dc`... -testing "files" "zcat f1.gz f2.gz && test -f f1.gz && test -f f2.gz" \ - "foo bar\n" "" "" - -# zcat -c is allowed, but the -c changes nothing. -testing "-c" "zcat -c f1.gz f2.gz && test -f f1.gz && test -f f2.gz" \ - "foo bar\n" "" "" +# zcat is basically just gzip -dc +testcmd "files" "1.gz 2.gz && test -f 1.gz && test -f 2.gz" "hi there\n" "" "" +# zcat -c is allowed but changes nothing +testcmd "-c" "-c 1.gz 2.gz && test -f 1.gz && test -f 2.gz" "hi there\n" "" "" +testing "concatenated" "{ cat 1.gz 2.gz; } | zcat" "hi there\n" "" "" +testing "error" "head -c 10 2.gz | { zcat 2>/dev/null || echo fail; }" "fail\n"\ + "" "" # TODO: how to test "zcat -f"? -rm -f f1 f2 f1.gz f2.gz +rm -f 1 2 1.gz 2.gz diff -Nru toybox-0.8.8+dfsg/toys/android/runcon.c toybox-0.8.9+dfsg/toys/android/runcon.c --- toybox-0.8.8+dfsg/toys/android/runcon.c 2022-08-12 07:58:03.000000000 +0000 +++ toybox-0.8.9+dfsg/toys/android/runcon.c 2023-01-10 19:24:45.000000000 +0000 @@ -2,7 +2,7 @@ * * Copyright 2015 The Android Open Source Project -USE_RUNCON(NEWTOY(runcon, "<2", TOYFLAG_USR|TOYFLAG_SBIN)) +USE_RUNCON(NEWTOY(runcon, "^<2", TOYFLAG_USR|TOYFLAG_SBIN)) config RUNCON bool "runcon" diff -Nru toybox-0.8.8+dfsg/toys/lsb/dmesg.c toybox-0.8.9+dfsg/toys/lsb/dmesg.c --- toybox-0.8.8+dfsg/toys/lsb/dmesg.c 2022-08-12 07:58:03.000000000 +0000 +++ toybox-0.8.9+dfsg/toys/lsb/dmesg.c 2023-01-10 19:24:45.000000000 +0000 @@ -4,10 +4,9 @@ * * See http://refspecs.linuxfoundation.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/dmesg.html * - * Don't ask me why the horrible new dmesg API is still in "testing": + * Linux 6.0 celebrates the 10th anniversary of this being in "testing": * http://kernel.org/doc/Documentation/ABI/testing/dev-kmsg -// We care that FLAG_c is 1, so keep c at the end. USE_DMESG(NEWTOY(dmesg, "w(follow)CSTtrs#<1n#c[!Ttr][!Cc][!Sw]", TOYFLAG_BIN)) config DMESG @@ -48,7 +47,7 @@ static void format_message(char *msg, int new) { unsigned long long time_s, time_us; - int facpri, subsystem, pos; + int facpri, subsystem, pos, ii, jj, in, out; char *p, *text; // The new /dev/kmsg and the old syslog(2) formats differ slightly. @@ -72,6 +71,12 @@ if (FLAG(r)) { color(0); printf("<%d>", facpri); + } else for (in = out = subsystem;; ) { + jj = 0; + if (text[in]=='\\'&& 1==sscanf(text+in, "\\x%2x%n", &ii, &jj) && jj==4) { + in += 4; + text[out++] = ii; + } else if (!(text[out++] = text[in++])) break; } // Format the time. @@ -158,7 +163,7 @@ // Figure out how much data we need, and fetch it. if (!(size = TT.s)) size = xklogctl(10, 0, 0); data = from = xmalloc(size+1); - data[size = xklogctl(3+FLAG(c), data, size)] = 0; + data[size = xklogctl(3+!!FLAG(c), data, size)] = 0; // Send each line to format_message. to = data + size; diff -Nru toybox-0.8.8+dfsg/toys/lsb/gzip.c toybox-0.8.9+dfsg/toys/lsb/gzip.c --- toybox-0.8.8+dfsg/toys/lsb/gzip.c 2022-08-12 07:58:03.000000000 +0000 +++ toybox-0.8.9+dfsg/toys/lsb/gzip.c 2023-01-10 19:24:45.000000000 +0000 @@ -8,7 +8,7 @@ * todo: qtv --rsyncable // gzip.net version allows all options for all commands. -USE_GZIP(NEWTOY(gzip, "ncdfk123456789[-123456789]", TOYFLAG_USR|TOYFLAG_BIN)) +USE_GZIP(NEWTOY(gzip, "n(no-name)cdfk123456789[-123456789]", TOYFLAG_USR|TOYFLAG_BIN)) USE_GUNZIP(NEWTOY(gunzip, "cdfk123456789[-123456789]", TOYFLAG_USR|TOYFLAG_BIN)) USE_ZCAT(NEWTOY(zcat, "cdfk123456789[-123456789]", TOYFLAG_USR|TOYFLAG_BIN)) diff -Nru toybox-0.8.8+dfsg/toys/lsb/mktemp.c toybox-0.8.9+dfsg/toys/lsb/mktemp.c --- toybox-0.8.8+dfsg/toys/lsb/mktemp.c 2022-08-12 07:58:03.000000000 +0000 +++ toybox-0.8.9+dfsg/toys/lsb/mktemp.c 2023-01-10 19:24:45.000000000 +0000 @@ -62,14 +62,8 @@ long long rr; char *s = template+len; - // Fall back to random-ish if xgetrandom fails. - if (!xgetrandom(&rr, sizeof(rr), WARN_ONLY)) { - struct timespec ts; - - clock_gettime(CLOCK_REALTIME, &ts); - rr = ts.tv_nsec*65537+(long)template+getpid()+(long)&template; - } // Replace X with 64 chars from posix portable character set (all but "_"). + xgetrandom(&rr, sizeof(rr)); while (--s>template) { if (*s != 'X') break; *s = '-'+(rr&63); diff -Nru toybox-0.8.8+dfsg/toys/lsb/mount.c toybox-0.8.9+dfsg/toys/lsb/mount.c --- toybox-0.8.8+dfsg/toys/lsb/mount.c 2022-08-12 07:58:03.000000000 +0000 +++ toybox-0.8.9+dfsg/toys/lsb/mount.c 2023-01-10 19:24:45.000000000 +0000 @@ -166,7 +166,7 @@ } if (strstart(&dev, "UUID=")) { - char *s = xrunread((char *[]){"blkid", "-U", dev, 0}, 0); + char *s = chomp(xrunread((char *[]){"blkid", "-U", dev, 0}, 0)); if (!s || strlen(s)>=sizeof(toybuf)) return error_msg("No uuid %s", dev); strcpy(dev = toybuf, s); @@ -175,7 +175,7 @@ // Autodetect bind mount or filesystem type - if (type && !strcmp(type, "auto")) type = 0; + if (type && (!strcmp(type, "auto") || !strcmp(type, "none"))) type = 0; if (flags & MS_MOVE) { if (type) error_exit("--move with -t"); } else if (!type) { @@ -253,7 +253,7 @@ if (rc && errno == ENOTBLK) { char *losetup[] = {"losetup", (flags&MS_RDONLY)?"-fsr":"-fs", dev, 0}; - if ((dev = xrunread(losetup, 0))) continue; + if ((dev = chomp(xrunread(losetup, 0)))) continue; error_msg("%s failed", *losetup); break; } diff -Nru toybox-0.8.8+dfsg/toys/lsb/su.c toybox-0.8.9+dfsg/toys/lsb/su.c --- toybox-0.8.8+dfsg/toys/lsb/su.c 2022-08-12 07:58:03.000000000 +0000 +++ toybox-0.8.9+dfsg/toys/lsb/su.c 2023-01-10 19:24:45.000000000 +0000 @@ -45,7 +45,6 @@ { char *name, *passhash = 0, **argu, **argv; struct passwd *up; - struct spwd *shp; if (*toys.optargs && !strcmp("-", *toys.optargs)) { toys.optflags |= FLAG_l; @@ -57,8 +56,10 @@ loggit(LOG_NOTICE, "%s->%s", getusername(geteuid()), name); - if (!(shp = getspnam(name))) perror_exit("no '%s'", name); if (getuid()) { + struct spwd *shp; + + if (!(shp = getspnam(name))) perror_exit("no '%s'", name); if (*shp->sp_pwdp != '$') goto deny; if (read_password(toybuf, sizeof(toybuf), "Password: ")) goto deny; passhash = crypt(toybuf, shp->sp_pwdp); diff -Nru toybox-0.8.8+dfsg/toys/net/host.c toybox-0.8.9+dfsg/toys/net/host.c --- toybox-0.8.8+dfsg/toys/net/host.c 2022-08-12 07:58:03.000000000 +0000 +++ toybox-0.8.9+dfsg/toys/net/host.c 2023-01-10 19:24:45.000000000 +0000 @@ -38,9 +38,9 @@ int type; } rrt[] = { { "A", "has address", 1 }, { "NS", "name server", 2 }, { "CNAME", "is a nickname for", 5 }, { "SOA", "start of authority", 6 }, - { "PTR", "domain name pointer", 12 }, { "MX", "mail is handled", 15 }, - { "TXT", "descriptive text", 16 }, { "AAAA", "has address", 28 }, - { "SRV", "mail is handled", 33 } + { "PTR", "domain name pointer", 12 }, { "HINFO", "host information", 13 }, + { "MX", "mail is handled", 15 }, { "TXT", "descriptive text", 16 }, + { "AAAA", "has address", 28 }, { "SRV", "mail is handled", 33 } }; int xdn_expand(char *packet, char *endpkt, char *comp, char *expand, int elen) @@ -97,7 +97,7 @@ // Prepare query packet of appropriate type if (TT.t[0]-'0'<10) type = atoi(TT.t); // TODO - else if (!strcasecmp(TT.t, "any") || strcmp(TT.t, "*")) type = 255; + else if (!strcasecmp(TT.t, "any") || !strcmp(TT.t, "*")) type = 255; else { for (i = 0; i0 && sec>1) @@ -156,11 +157,11 @@ pllen = peek_be(p, 2); p += 2; if ((p-abuf)+pllen>alen) error_exit("tilt"); - if (type==1 || type == 28) inet_ntop(type==1 ? AF_INET : AF_INET6, p, t2, t2len); else if (type==2 || type==5) xdn_expand(abuf, abuf+alen, p, t2, t2len); - else if (type==16) sprintf(t2, "\"%.*s\"", minof(pllen, t2len), p); + else if (type==13 || type==16) + sprintf(t2, "\"%.*s\"", minof(pllen, t2len), p); else if (type==6) { ss = p+xdn_expand(abuf, abuf+alen, p, t2, t2len-1); j = strlen(t2); @@ -184,12 +185,13 @@ printf("%s unsupported RR type %u\n", toybuf, type); continue; } - for (i = 0; rrt[i].type != type; i++); if (verbose) printf("%s\t%u\tIN %s\t%s\n", toybuf, ttl, rrt[i].name, t2); - else printf("%s %s %s\n", toybuf, rrt[type].msg, t2); + else printf("%s %s %s\n", toybuf, rrt[i].msg, t2); + qlen++; } } + if (TT.t && !qlen) printf("%s has no %s record\n", *toys.optargs, TT.t); if (CFG_TOYBOX_FREE) free(abuf); toys.exitval = rcode; diff -Nru toybox-0.8.8+dfsg/toys/net/httpd.c toybox-0.8.9+dfsg/toys/net/httpd.c --- toybox-0.8.8+dfsg/toys/net/httpd.c 2022-08-12 07:58:03.000000000 +0000 +++ toybox-0.8.9+dfsg/toys/net/httpd.c 2023-01-10 19:24:45.000000000 +0000 @@ -12,17 +12,22 @@ * -ifv -p [IP:]PORT -u [USER][:GRP] -c CFGFILE * cgi: SERVER_PORT SERVER_NAME REMOTE_ADDR REMOTE_HOST REQUEST_METHOD -USE_HTTPD(NEWTOY(httpd, ">1", TOYFLAG_USR|TOYFLAG_BIN)) +USE_HTTPD(NEWTOY(httpd, ">1v", TOYFLAG_USR|TOYFLAG_BIN)) config HTTPD bool "httpd" default y help - usage: httpd [DIR] + usage: httpd [-e STR] [DIR] Serve contents of directory as static web pages. + + -e Escape STR as URL, printing result and exiting. + -d Decode escaped STR, printing result and exiting. + -v Verbose */ +#define FOR_httpd #include "toys.h" char *rfc1123(char *buf, time_t t) @@ -32,23 +37,6 @@ return buf; } -// Stop: header time. -void header_time(int stat, char *str, char *more) -{ - char buf[64]; - - xprintf("HTTP/1.1 %d %s\r\nServer: toybox httpd/%s\r\nDate: %s\r\n%s" - "Connection: close\r\n\r\n", stat, str, TOYBOX_VERSION, - rfc1123(buf, time(0)), more ? : ""); -} - -void error_time(int stat, char *str) -{ - header_time(stat, str, 0); - xprintf("%d %s" - "

%d %s

", stat, str, stat, str); -} - // She never told me... char *mime(char *file) { @@ -80,6 +68,25 @@ return toybuf; } +// Stop: header time. +static void header_time(int stat, char *str, char *more) +{ + char buf[64]; + + if (!more) more = ""; + if (FLAG(v)) dprintf(2, "REPLY: %d %s\n%s\n", stat, str, more); + xprintf("HTTP/1.1 %d %s\r\nServer: toybox httpd/%s\r\nDate: %s\r\n%s" + "Connection: close\r\n\r\n", stat, str, TOYBOX_VERSION, + rfc1123(buf, time(0)), more); +} + +static void error_time(int stat, char *str) +{ + header_time(stat, str, 0); + xprintf("%d %s" + "

%d %s

", stat, str, stat, str); +} + static int isunder(char *file, char *dir) { char *s1 = xabspath(dir, ABS_FILE), *s2 = xabspath(file, 0), *ss = s2; @@ -95,8 +102,15 @@ void handle(int infd, int outfd) { FILE *fp = fdopen(infd, "r"); - char *s = xgetline(fp), *ss, *esc, *path, *word[3]; - int i, fd; + char *s = xgetline(fp), *cut, *ss, *esc, *path, *word[3]; + int i = sizeof(toybuf), fd; + + if (!s) return; + + if (!getsockname(0, (void *)&toybuf, &i)) { + if (FLAG(v)) + dprintf(2, "Hello %s\n%s\n", ntop((void *)toybuf), s); + } // Split line into method/path/protocol for (i = 0, ss = s;;) { @@ -110,6 +124,7 @@ // Process additional http/1.1 lines while ((ss = xgetline(fp))) { i = *chomp(ss); + if (FLAG(v)) dprintf(2, "%s\n", ss); // TODO: any of //User-Agent: Wget/1.20.1 (linux-gnu) - do we want to log anything? //Accept: */* - 406 Too Snobbish @@ -123,12 +138,14 @@ if (!strcasecmp(word[0], "get")) { struct stat st; + if (*(ss = word[1])!='/') error_time(400, "Bad Request"); while (*ss=='/') ss++; - if (!*ss) ss = "."; - else unescape_url(ss); + if (!*ss) ss = "./"; + else if ((cut = unescape_url(ss, 1))) setenv("QUERY_STRING", cut, 1); // TODO domain.com:/path/to/blah domain2.com:/path/to/that + // TODO cgi PATH_INFO /path/to/filename.cgi/and/more/stuff?path&info if (!isunder(ss, ".") || stat(ss, &st)) error_time(404, "Not Found"); else if (-1 == (fd = open(ss, O_RDONLY))) error_time(403, "Forbidden"); else if (!S_ISDIR(st.st_mode)) { @@ -139,7 +156,7 @@ mime(ss), (long long)st.st_size, rfc1123(buf, st.st_mtime))); free(ss); xsendfile(fd, outfd); - } else if (ss[strlen(ss)-1]!='/' && strcmp(ss, ".")) { + } else if (ss[strlen(ss)-1]!='/') { header_time(302, "Found", path = xmprintf("Location: %s/\r\n", word[1])); free(path); } else { @@ -150,7 +167,7 @@ path = ss; ss = "index.html"; path = xmprintf("%s%s", path, ss); - if (!stat(path, &st) || !S_ISREG(st.st_mode)) i = -1; + if (stat(path, &st) || !S_ISREG(st.st_mode)) i = -1; else if (-1 == (i = open(path, O_RDONLY))) error_time(403, "Forbidden"); free(path); if (i != -1) { diff -Nru toybox-0.8.8+dfsg/toys/net/netcat.c toybox-0.8.9+dfsg/toys/net/netcat.c --- toybox-0.8.8+dfsg/toys/net/netcat.c 2022-08-12 07:58:03.000000000 +0000 +++ toybox-0.8.9+dfsg/toys/net/netcat.c 2023-01-10 19:24:45.000000000 +0000 @@ -2,11 +2,9 @@ * * Copyright 2007 Rob Landley * - * TODO: udp, ipv6, genericize for telnet/microcom/tail-f - * fix -t, xconnect - * netcat -L zombies + * TODO: genericize for telnet/microcom/tail-f, fix -t -USE_NETCAT(NEWTOY(netcat, "^tElLw#<1W#<1p#<1>65535q#<1s:f:46uU[!tlL][!Lw][!46U]", TOYFLAG_BIN)) +USE_NETCAT(NEWTOY(netcat, "^tElLw#<1W#<1p#<1>65535q#<1s:f:46uUn[!tlL][!Lw][!Lu][!46U]", TOYFLAG_BIN)) USE_NETCAT(OLDTOY(nc, netcat, TOYFLAG_USR|TOYFLAG_BIN)) config NETCAT @@ -23,6 +21,7 @@ -f Use FILENAME (ala /dev/ttyS0) instead of network -l Listen for one incoming connection, then exit -L Listen and background each incoming connection (server mode) + -n No DNS lookup -p Local port number -q Quit SECONDS after EOF on stdin, even if stdout hasn't closed yet -s Local source address @@ -80,7 +79,8 @@ void netcat_main(void) { int sockfd = -1, in1 = 0, in2 = 0, out1 = 1, out2 = 1, family = AF_UNSPEC, - ll = FLAG(L)|FLAG(l), type = FLAG(u) ? SOCK_DGRAM : SOCK_STREAM; + type = FLAG(u) ? SOCK_DGRAM : SOCK_STREAM; + socklen_t len; pid_t child; // Addjust idle and quit_delay to ms or -1 for no timeout @@ -95,7 +95,7 @@ // The argument parsing logic can't make "<2" conditional on other // arguments like -f and -l, so do it by hand here. - if (FLAG(f) ? toys.optc : (!ll && toys.optc!=(FLAG(U)?1:2))) + if (FLAG(f) ? toys.optc : (!FLAG(l) && !FLAG(L) && toys.optc!=(FLAG(U)?1:2))) help_exit("bad argument count"); if (FLAG(4)) family = AF_INET; @@ -105,10 +105,10 @@ if (TT.f) in1 = out2 = xopen(TT.f, O_RDWR); else { // Setup socket - if (!ll) { + if (!FLAG(l) && !FLAG(L)) { if (FLAG(U)) sockfd = usock(toys.optargs[0], type, 1); - else sockfd = xconnectany(xgetaddrinfo(toys.optargs[0], toys.optargs[1], - family, type, 0, 0)); + else sockfd = xconnectany(xgetaddrinfo(toys.optargs[0], + toys.optargs[1], family, type, 0, AI_NUMERICHOST*!!FLAG(n))); // We have a connection. Disarm timeout and start poll/send loop. alarm(0); @@ -124,12 +124,12 @@ sockfd = xbindany(xgetaddrinfo(TT.s, toybuf, family, type, 0, 0)); } - if (listen(sockfd, 5)) error_exit("listen"); + if (!FLAG(u) && listen(sockfd, 5)) perror_exit("listen"); if (!TT.p && !FLAG(U)) { struct sockaddr* address = (void*)toybuf; - socklen_t len = sizeof(struct sockaddr_storage); short port_be; + len = sizeof(struct sockaddr_storage); getsockname(sockfd, address, &len); if (address->sa_family == AF_INET) port_be = ((struct sockaddr_in*)address)->sin_port; @@ -144,41 +144,46 @@ } do { + len = sizeof(struct sockaddr_storage); + if (FLAG(u)) { + if (-1 == recvfrom(in1 = dup(sockfd), &child, 1, MSG_PEEK, + (void *)toybuf, &len)) perror_exit("recvfrom"); + } else if ((in1 = accept(sockfd, 0, 0))<0) perror_exit("accept"); + out2 = in1; child = 0; - in1 = out2 = accept(sockfd, 0, 0); - if (in1<0) perror_exit("accept"); // We have a connection. Disarm timeout. alarm(0); + // Fork a child as necessary. Parent cleans up and continues here. + if (toys.optc && FLAG(L)) NOEXIT(child = XVFORK()); + if (child) { + close(in1); + continue; + } + + if (FLAG(u)) + xconnect(in1, (void *)toybuf, sizeof(struct sockaddr_storage)); + + // Cleanup and redirect for exec if (toys.optc) { // Do we need a tty? +// TODO nommu and -t only affects server mode... +// if (FLAG(t)) child = forkpty(&fdout, NULL, NULL, NULL); -// TODO nommu, and -t only affects server mode...? Only do -t with optc -// if (CFG_TOYBOX_FORK && (toys.optflags&FLAG_t)) -// child = forkpty(&fdout, NULL, NULL, NULL); -// else - - // Do we need to fork and/or redirect for exec? - -// TODO xpopen_both() here? - - if (FLAG(L)) NOEXIT(child = XVFORK()); - if (child) { - close(in1); - continue; - } close(sockfd); dup2(in1, 0); dup2(in1, 1); if (FLAG(E)) dup2(in1, 2); if (in1>2) close(in1); xexec(toys.optargs); - } - pollinate(in1, in2, out1, out2, TT.W, TT.q); - close(in1); - } while (!FLAG(l)); + // Copy stdin/out + } else { + pollinate(in1, in2, out1, out2, TT.W, TT.q); + close(in1); + } + } while (FLAG(L)); } } diff -Nru toybox-0.8.8+dfsg/toys/net/netstat.c toybox-0.8.9+dfsg/toys/net/netstat.c --- toybox-0.8.8+dfsg/toys/net/netstat.c 2022-08-12 07:58:03.000000000 +0000 +++ toybox-0.8.9+dfsg/toys/net/netstat.c 2023-01-10 19:24:45.000000000 +0000 @@ -36,6 +36,42 @@ int wpad; ) +struct num_cache { + struct num_cache *next; + long long num; + char data[]; +}; + +// Find num in cache +static struct num_cache *get_num_cache(struct num_cache *cache, long long num) +{ + while (cache) { + if (num==cache->num) return cache; + cache = cache->next; + } + + return 0; +} + +// Uniquely add num+data to cache. Updates *cache, returns pointer to existing +// entry if it was already there. +static struct num_cache *add_num_cache(struct num_cache **cache, long long num, + void *data, int len) +{ + struct num_cache *old = get_num_cache(*cache, num); + + if (old) return old; + + old = xzalloc(sizeof(struct num_cache)+len); + old->next = *cache; + old->num = num; + memcpy(old->data, data, len); + + *cache = old; + + return 0; +} + static void addr2str(int af, void *addr, unsigned port, char *buf, int len, char *proto) { diff -Nru toybox-0.8.8+dfsg/toys/net/sntp.c toybox-0.8.9+dfsg/toys/net/sntp.c --- toybox-0.8.8+dfsg/toys/net/sntp.c 2022-08-12 07:58:03.000000000 +0000 +++ toybox-0.8.9+dfsg/toys/net/sntp.c 2023-01-10 19:24:45.000000000 +0000 @@ -20,10 +20,10 @@ -s Set system clock suddenly -a Adjust system clock gradually -S Serve time instead of querying (bind to SERVER address if specified) - -m Wait for updates from multicast ADDRESS (RFC 4330 default 224.0.1.1) - -M Multicast server on ADDRESS (default 224.0.0.1) + -m Wait for updates from multicast ADDRESS (RFC 4330 suggests 224.0.1.1) + -M Multicast server on ADDRESS (RFC 4330 suggests 224.0.1.1) -t TTL (multicast only, default 1) - -d Daemonize (run in background re-querying ) + -d Daemonize (run in background re-querying) -D Daemonize but stay in foreground: re-query time every 1000 seconds -r Retry shift (every 1<3", TOYFLAG_USR|TOYFLAG_BIN)) +USE_DEVMEM(NEWTOY(devmem, "<1>3", TOYFLAG_USR|TOYFLAG_SBIN)) config DEVMEM bool "devmem" diff -Nru toybox-0.8.8+dfsg/toys/other/fmt.c toybox-0.8.9+dfsg/toys/other/fmt.c --- toybox-0.8.8+dfsg/toys/other/fmt.c 2022-08-12 07:58:03.000000000 +0000 +++ toybox-0.8.9+dfsg/toys/other/fmt.c 2023-01-10 19:24:45.000000000 +0000 @@ -27,7 +27,7 @@ #include "toys.h" GLOBALS( - int width; + long width; int level, pos; ) diff -Nru toybox-0.8.8+dfsg/toys/other/gpiod.c toybox-0.8.9+dfsg/toys/other/gpiod.c --- toybox-0.8.8+dfsg/toys/other/gpiod.c 2022-08-12 07:58:03.000000000 +0000 +++ toybox-0.8.9+dfsg/toys/other/gpiod.c 2023-01-10 19:24:45.000000000 +0000 @@ -5,9 +5,9 @@ * TODO: gpiomon USE_GPIODETECT(NEWTOY(gpiodetect, ">0", TOYFLAG_USR|TOYFLAG_BIN)) -USE_GPIOFIND(NEWTOY(gpioinfo, 0, TOYFLAG_USR|TOYFLAG_BIN)) +USE_GPIOINFO(NEWTOY(gpioinfo, 0, TOYFLAG_USR|TOYFLAG_BIN)) USE_GPIOGET(NEWTOY(gpioget, "<2l", TOYFLAG_USR|TOYFLAG_BIN)) -USE_GPIOINFO(NEWTOY(gpiofind, "<1>1", TOYFLAG_USR|TOYFLAG_BIN)) +USE_GPIOFIND(NEWTOY(gpiofind, "<1>1", TOYFLAG_USR|TOYFLAG_BIN)) USE_GPIOSET(NEWTOY(gpioset, "<2l", TOYFLAG_USR|TOYFLAG_BIN)) config GPIODETECT @@ -100,7 +100,7 @@ static void foreach_chip(void (*cb)(char *name)) { struct double_list **sorted; - int i = 0; + int i; dirtree_flagread("/dev", DIRTREE_SHUTUP, collect_chips); if (!TT.chips) return; diff -Nru toybox-0.8.8+dfsg/toys/other/help.c toybox-0.8.9+dfsg/toys/other/help.c --- toybox-0.8.8+dfsg/toys/other/help.c 2022-08-12 07:58:03.000000000 +0000 +++ toybox-0.8.9+dfsg/toys/other/help.c 2023-01-10 19:24:45.000000000 +0000 @@ -30,7 +30,7 @@ xprintf("

%s

\n", t->name, t->name);
 
   toys.which = t;
-  show_help(stdout, !FLAG(u)+(!!toys.argv[1]<<1));
+  show_help(stdout, !FLAG(u)+(!!toys.argv[1]<<1)+(!!FLAG(h)<<2));
 
   if (FLAG(h)) xprintf("
\n"); } @@ -65,12 +65,14 @@ sprintf(toybuf, "Toybox %s command help", toybox_version); xprintf("\n%s\n\n

%s


", toybuf, toybuf); - for (i=0; i < toys.toycount; i++) - xprintf("%s \n", toy_list[i].name, toy_list[i].name); + for (i=0; i%s \n", toy_list[i].name,toy_list[i].name); xprintf("

\n"); } for (i = 0; i < toys.toycount; i++) { + if (!toy_list[i].flags) continue; if (FLAG(h)) xprintf("
\n
\n");
     else if (!FLAG(u)) {
       memset(toybuf, '-', 78);
diff -Nru toybox-0.8.8+dfsg/toys/other/hexedit.c toybox-0.8.9+dfsg/toys/other/hexedit.c
--- toybox-0.8.8+dfsg/toys/other/hexedit.c	2022-08-12 07:58:03.000000000 +0000
+++ toybox-0.8.9+dfsg/toys/other/hexedit.c	2023-01-10 19:24:45.000000000 +0000
@@ -181,7 +181,7 @@
   size_t len = strlen(TT.search);
 
   for (; pos >= 0; pos--) {
-    if (!memcmp(TT.data+pos, TT.search, len)) {
+    if (!smemcmp(TT.data+pos, TT.search, len)) {
       TT.pos = pos;
       return;
     }
diff -Nru toybox-0.8.8+dfsg/toys/other/i2ctools.c toybox-0.8.9+dfsg/toys/other/i2ctools.c
--- toybox-0.8.8+dfsg/toys/other/i2ctools.c	2022-08-12 07:58:03.000000000 +0000
+++ toybox-0.8.9+dfsg/toys/other/i2ctools.c	2023-01-10 19:24:45.000000000 +0000
@@ -11,10 +11,10 @@
  * TODO: i2cget non-byte modes? default to current read address?
  * TODO: i2cset -r? -m MASK? c/s modes, p mode modifier?
 
-USE_I2CDETECT(NEWTOY(i2cdetect, ">3aFlqry[!qr]", TOYFLAG_USR|TOYFLAG_BIN))
-USE_I2CDUMP(NEWTOY(i2cdump, "<2>2fy", TOYFLAG_USR|TOYFLAG_BIN))
-USE_I2CGET(NEWTOY(i2cget, "<3>3fy", TOYFLAG_USR|TOYFLAG_BIN))
-USE_I2CSET(NEWTOY(i2cset, "<4fy", TOYFLAG_USR|TOYFLAG_BIN))
+USE_I2CDETECT(NEWTOY(i2cdetect, ">3aFlqry[!qr]", TOYFLAG_USR|TOYFLAG_SBIN))
+USE_I2CDUMP(NEWTOY(i2cdump, "<2>2fy", TOYFLAG_USR|TOYFLAG_SBIN))
+USE_I2CGET(NEWTOY(i2cget, "<3>3fy", TOYFLAG_USR|TOYFLAG_SBIN))
+USE_I2CSET(NEWTOY(i2cset, "<4fy", TOYFLAG_USR|TOYFLAG_SBIN))
 
 config I2CDETECT
   bool "i2cdetect"
diff -Nru toybox-0.8.8+dfsg/toys/other/losetup.c toybox-0.8.9+dfsg/toys/other/losetup.c
--- toybox-0.8.8+dfsg/toys/other/losetup.c	2022-08-12 07:58:03.000000000 +0000
+++ toybox-0.8.9+dfsg/toys/other/losetup.c	2023-01-10 19:24:45.000000000 +0000
@@ -71,6 +71,16 @@
       }
       close(cfd);
     }
+    if (CFG_TOYBOX_ON_ANDROID && device) {
+      // ANDROID SPECIFIC: /dev is not devtmpfs, instead an userspace daemon
+      // ueventd is responsible for creating the loop devices under /dev.
+      // Wait for the uevent to be processed to avoid race.
+      long long timeout = millitime() + 5000;
+      do {
+        if (!access(device, F_OK) || errno != ENOENT) break;
+        msleep(20);
+      } while (millitime() < timeout);
+    }
   }
 
   if (device) lfd = open(device, TT.openflags);
diff -Nru toybox-0.8.8+dfsg/toys/other/lsusb.c toybox-0.8.9+dfsg/toys/other/lsusb.c
--- toybox-0.8.8+dfsg/toys/other/lsusb.c	2022-08-12 07:58:03.000000000 +0000
+++ toybox-0.8.9+dfsg/toys/other/lsusb.c	2023-01-10 19:24:45.000000000 +0000
@@ -109,18 +109,22 @@
 struct dev_ids *parse_dev_ids(char *name, struct dev_ids **and)
 {
   char *path = "/etc:/vendor:/usr/share/misc";
-  struct string_list *sl;
+  struct string_list *sl = 0;
   FILE *fp;
   char *s, *ss, *sss;
   struct dev_ids *ids = 0, *new;
-  int fd = -1, tick = 0;
+  int fd = -1;
 
   // Open compressed or uncompressed file
-  sprintf(toybuf, "%s.gz", name);
-  if ((sl = find_in_path(path, toybuf))) {
-    signal(SIGCHLD, SIG_IGN);
-    xpopen((char *[]){"zcat", sl->str, 0}, &fd, 1);
-  } else if ((sl = find_in_path(path, name))) fd = xopen(sl->str,O_RDONLY);
+  signal(SIGCHLD, SIG_IGN);
+  s = TT.i;
+  if (!s) {
+    sprintf(toybuf, "%s.gz", name);
+    if ((sl = find_in_path(path, toybuf)) || (sl = find_in_path(path, name)))
+      s = sl->str;
+  }
+  if (s && strend(s, ".gz")) xpopen((char *[]){"zcat", sl->str, 0}, &fd, 1);
+  else if (s) fd = xopen(s, O_RDONLY);
   llist_traverse(sl, free);
   if (fd == -1) return 0;
   
@@ -132,8 +136,7 @@
     if (strstart(&ss, "C ") && and) {
       *and = ids;
       and = 0;
-      tick++;
-    } 
+    }
     fd = estrtol(sss = ss, &ss, 16);
     if (ss>sss && *ss++==' ') {
       while (isspace(*ss)) ss++;
diff -Nru toybox-0.8.8+dfsg/toys/other/mcookie.c toybox-0.8.9+dfsg/toys/other/mcookie.c
--- toybox-0.8.8+dfsg/toys/other/mcookie.c	2022-08-12 07:58:03.000000000 +0000
+++ toybox-0.8.9+dfsg/toys/other/mcookie.c	2023-01-10 19:24:45.000000000 +0000
@@ -29,7 +29,7 @@
   long long *ll = (void *)toybuf;
 
   if (FLAG(V)) return (void)puts("mcookie from toybox");
-  xgetrandom(toybuf, 16, 0);
+  xgetrandom(toybuf, 16);
   if (FLAG(v)) fputs("Got 16 bytes from xgetrandom()\n", stderr);
   xprintf("%016llx%06llx\n", ll[0], ll[1]);
 }
diff -Nru toybox-0.8.8+dfsg/toys/other/modinfo.c toybox-0.8.9+dfsg/toys/other/modinfo.c
--- toybox-0.8.8+dfsg/toys/other/modinfo.c	2022-08-12 07:58:03.000000000 +0000
+++ toybox-0.8.9+dfsg/toys/other/modinfo.c	2023-01-10 19:24:45.000000000 +0000
@@ -44,6 +44,7 @@
   char *buf = 0, *end, *modinfo_tags[] = {
     "license", "author", "description", "firmware", "alias", "srcversion",
     "depends", "retpoline", "intree", "name", "vermagic", "parm", "parmtype",
+    "scmversion",
   };
 
   if (-1 != (fd = open(full_name, O_RDONLY))) {
diff -Nru toybox-0.8.8+dfsg/toys/other/mountpoint.c toybox-0.8.9+dfsg/toys/other/mountpoint.c
--- toybox-0.8.8+dfsg/toys/other/mountpoint.c	2022-08-12 07:58:03.000000000 +0000
+++ toybox-0.8.9+dfsg/toys/other/mountpoint.c	2023-01-10 19:24:45.000000000 +0000
@@ -34,7 +34,7 @@
   struct stat st1, st2;
   char *arg = *toys.optargs;
 
-  if (lstat(arg, &st1)) perror_exit_raw(arg);
+  if (lstat(arg, &st1)) (FLAG(q) ? die : perror_exit_raw)(arg);
 
   if (FLAG(x)) {
     if (!S_ISBLK(st1.st_mode)) die("block device");
@@ -55,7 +55,7 @@
   // inode are the same, it's probably "/". This misses --bind mounts from
   // elsewhere in the same filesystem, but so does the other one and in the
   // absence of a spec I guess that's the expected behavior?
-  toys.exitval = !same_file(&st1, &st2);
+  toys.exitval = !(st1.st_dev != st2.st_dev || st1.st_ino == st2.st_ino);
   if (FLAG(d)) printf("%u:%u\n", dev_major(st1.st_dev), dev_minor(st1.st_dev));
   else if (!FLAG(q))
     printf("%s is %sa mountpoint\n", *toys.optargs, toys.exitval ? "not " : "");
diff -Nru toybox-0.8.8+dfsg/toys/other/nbd_client.c toybox-0.8.9+dfsg/toys/other/nbd_client.c
--- toybox-0.8.8+dfsg/toys/other/nbd_client.c	2022-08-12 07:58:03.000000000 +0000
+++ toybox-0.8.9+dfsg/toys/other/nbd_client.c	2023-01-10 19:24:45.000000000 +0000
@@ -7,28 +7,26 @@
 // This little dance is because a NEWTOY with - in the name tries to do
 // things like prototype "nbd-client_main" which isn't a valid symbol. So
 // we hide the underscore name and OLDTOY the name we want.
-USE_NBD_CLIENT(NEWTOY(nbd_client, "<3>3ns", 0))
+USE_NBD_CLIENT(NEWTOY(nbd_client, "<3>3b#<1>4294967295=4096ns", 0))
 USE_NBD_CLIENT(OLDTOY(nbd-client, nbd_client, TOYFLAG_USR|TOYFLAG_BIN))
 
 config NBD_CLIENT
   bool "nbd-client"
-  depends on TOYBOX_FORK
   default y
   help
-    usage: nbd-client [-ns] HOST PORT DEVICE
+    usage: nbd-client [-ns] [-b BLKSZ] HOST PORT DEVICE
 
-    -n	Do not fork into background
+    -b	Block size (default 4096)
+    -n	Do not daemonize
     -s	nbd swap support (lock server into memory)
 */
 
 /*  TODO:
-    usage: nbd-client [-sSpn] [-b BLKSZ] [-t SECS] [-N name] HOST PORT DEVICE
+    usage: nbd-client [-Sp] [-t SECS] [-N name] HOST PORT DEVICE
 
-    -b	block size
     -t	timeout in seconds
     -S	sdp
     -p	persist
-    -n	nofork
     -d	DEVICE
     -c	DEVICE
 */
@@ -37,78 +35,88 @@
 #include "toys.h"
 #include 
 
+GLOBALS(
+  long b;
+
+  int nbd;
+)
+
+static void sig_cleanup(int catch)
+{
+  // Flush on the way out
+  ioctl(TT.nbd, NBD_CLEAR_QUE);
+  ioctl(TT.nbd, NBD_CLEAR_SOCK);
+  _exit(catch ? 128+catch : 0);
+}
+
 void nbd_client_main(void)
 {
-  int sock = -1, nbd, flags;
+  int sock = -1, flags, temp;
   unsigned long timeout = 0;
   char *host=toys.optargs[0], *port=toys.optargs[1], *device=toys.optargs[2];
-  uint64_t devsize;
+  unsigned long long devsize;
+
+  // Daemonize in a nommu-friendly way, but retain stderr
+  if (toys.stacktop && !FLAG(n)) {
+    dup2(2, 222);
+    xvdaemon();
+  }
+  dup2(222, 2);
+  close(222);
 
-  // Repeat until spanked
+  TT.nbd = xopen(device, O_RDWR);
+  xsignal(SIGINT, sig_cleanup);
+  xsignal(SIGTERM, sig_cleanup);
 
-  nbd = xopen(device, O_RDWR);
   for (;;) {
-    int temp;
-
     // Find and connect to server
-
     sock = xconnectany(xgetaddrinfo(host, port, AF_UNSPEC, SOCK_STREAM, 0, 0));
     temp = 1;
     setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &temp, sizeof(int));
 
     // Read login data
-
     xreadall(sock, toybuf, 152);
-    if (memcmp(toybuf, "NBDMAGIC\x00\x00\x42\x02\x81\x86\x12\x53", 16))
+    if (smemcmp(toybuf, "NBDMAGIC\x00\x00\x42\x02\x81\x86\x12\x53", 16))
       error_exit("bad login %s:%s", host, port);
-    devsize = SWAP_BE64(*(uint64_t *)(toybuf+16));
+    devsize = SWAP_BE64(*(unsigned long long *)(toybuf+16));
     flags = SWAP_BE32(*(int *)(toybuf+24));
 
-    // Set 4k block size.  Everything uses that these days.
-    ioctl(nbd, NBD_SET_BLKSIZE, 4096);
-    ioctl(nbd, NBD_SET_SIZE_BLOCKS, devsize/4096);
-    ioctl(nbd, NBD_CLEAR_SOCK);
-
-    // If the sucker was exported read only, respect that locally.
-    temp = (flags & 2) ? 1 : 0;
-    xioctl(nbd, BLKROSET, &temp);
+    // Use 4k block size
+    ioctl(TT.nbd, NBD_SET_BLKSIZE, TT.b);
+    ioctl(TT.nbd, NBD_SET_SIZE_BLOCKS, devsize/TT.b); // rounds down
+    ioctl(TT.nbd, NBD_CLEAR_SOCK);
+
+    // Locally respect read only exports
+    flags = (flags>>1)&1;
+    xioctl(TT.nbd, BLKROSET, &flags);
 
-    if (timeout && ioctl(nbd, NBD_SET_TIMEOUT, timeout)<0) break;
-    if (ioctl(nbd, NBD_SET_SOCK, sock) < 0) break;
+    if (timeout && ioctl(TT.nbd, NBD_SET_TIMEOUT, timeout)<0) break;
+    if (ioctl(TT.nbd, NBD_SET_SOCK, sock) < 0) break;
 
-    if (toys.optflags & FLAG_s) mlockall(MCL_CURRENT|MCL_FUTURE);
+    if (FLAG(s)) mlockall(MCL_CURRENT|MCL_FUTURE);
 
     // Open the device to force reread of the partition table.
-    if ((toys.optflags & FLAG_n) || !xfork()) {
+    if (!CFG_TOYBOX_FORK || !xfork()) {
       char *s = strrchr(device, '/');
       int i;
 
+      // Give device up to 10 seconds to come up
       sprintf(toybuf, "/sys/block/%.32s/pid", s ? s+1 : device);
-      // Is it up yet? (Give it 10 seconds.)
       for (i=0; i<100; i++) {
-        temp = open(toybuf, O_RDONLY);
-        if (temp == -1) msleep(100);
-        else {
-          close(temp);
-          break;
-        }
+        if (access(toybuf, F_OK)) break;
+        msleep(100);
       }
       close(open(device, O_RDONLY));
-      if (!(toys.optflags & FLAG_n)) exit(0);
+      if (CFG_TOYBOX_FORK) _exit(0);
     }
 
-    // Daemonize here.
-
-    if (daemon(0,0)) perror_exit("daemonize");
-
     // Process NBD requests until further notice.
 
-    if (ioctl(nbd, NBD_DO_IT)>=0 || errno==EBADR) break;
+    if (ioctl(TT.nbd, NBD_DO_IT)>=0 || errno==EBADR) break;
     close(sock);
+    ioctl(TT.nbd, NBD_CLEAR_QUE);
   }
 
   // Flush queue and exit.
-  ioctl(nbd, NBD_CLEAR_QUE);
-  ioctl(nbd, NBD_CLEAR_SOCK);
-  if (CFG_TOYBOX_FREE) close(nbd);
+  if (CFG_TOYBOX_FREE) close(TT.nbd);
 }
diff -Nru toybox-0.8.8+dfsg/toys/other/nbd_server.c toybox-0.8.9+dfsg/toys/other/nbd_server.c
--- toybox-0.8.8+dfsg/toys/other/nbd_server.c	1970-01-01 00:00:00.000000000 +0000
+++ toybox-0.8.9+dfsg/toys/other/nbd_server.c	2023-01-10 19:24:45.000000000 +0000
@@ -0,0 +1,88 @@
+/* nbd-server.c - network block device server
+ *
+ * Copyright 2022 Rob Landley 
+ *
+ * Not in SUSv4.
+ *
+ * See https://github.com/NetworkBlockDevice/nbd/blob/master/doc/proto.md
+
+// Work around dash in name trying to put - in function name.
+USE_NBD_SERVER(NEWTOY(nbd_server, "<1>1r", 0))
+USE_NBD_SERVER(OLDTOY(nbd-server, nbd_server, TOYFLAG_USR|TOYFLAG_BIN))
+
+config NBD_SERVER
+  bool "nbd-server"
+  default y
+  help
+    usage: nbd-server [-r] FILE
+
+    Serve a Network Block Device from FILE on stdin/out (ala inetd).
+
+    -r	Read only export
+*/
+
+// TODO: -r, block size, exit signal?
+
+#define FOR_nbd_server
+#include "toys.h"
+
+static int copy_loop(int from, int to, unsigned len)
+{
+  int try, rc = 0;
+
+  errno = 0;
+  while (len) {
+    xreadall(from, toybuf, try = len>4096 ? 4096 : len);
+    if (!rc && try != writeall(to, toybuf, try)) rc = errno;
+    len -= try;
+  }
+
+  return rc;
+}
+
+void nbd_server_main(void)
+{
+  unsigned long long *ll = (void *)toybuf, offset, handle;
+  unsigned short *ss = (void *)toybuf;
+  unsigned *uu = (void *)toybuf, type, length;
+  int fd = xopen(*toys.optargs, O_RDWR*!FLAG(r));
+
+  type = 1;
+  setsockopt(0, IPPROTO_TCP, TCP_NODELAY, &type, sizeof(int));
+
+  // Send original recipe negotiation, with device length and flags
+  memcpy(toybuf, "NBDMAGIC\x00\x00\x42\x02\x81\x86\x12\x53", 16);
+  ll[2] = SWAP_BE64(fdlength(fd));
+  uu[6] = SWAP_BE32(5+2*!!FLAG(r)); // has flags, can flush, maybe read only
+  xwrite(1, toybuf, 152);
+
+  // Simple loop, handles one request at a time with "simple" reply.
+  for (;;) {
+    // Fetch request into toybuf
+    xreadall(0, toybuf, 28);
+    if (SWAP_BE32(*uu) != 0x25609513) break;
+    type = SWAP_BE16(ss[3]);
+    handle = SWAP_BE64(ll[1]);
+    offset = SWAP_BE64(ll[2]);
+    length = SWAP_BE32(uu[6]);
+
+    // type 0 = read, 1 = write, 2 = disconnect, 3 = flush
+    if (type==2 || type>3) break;  // disconnect
+    if (type==3) { // flush
+      if (fdatasync(fd)) uu[1] = SWAP_BE32(errno);
+    } else {
+      xlseek(fd, offset, SEEK_SET);
+      if (type==1) { // write
+        uu[1] = copy_loop(0, fd, length);
+        ll[1] = SWAP_BE64(handle);
+      } else uu[1] = 0; // read never reports errors because send header first
+    }
+
+    // Simple reply in toybuf (handle stays put)
+    *uu = SWAP_BE32(0x67446698);
+    xwrite(1, toybuf, 16);
+
+    // Append read payload
+    if (!type) if (copy_loop(fd, 1, length)) break;
+  }
+}
diff -Nru toybox-0.8.8+dfsg/toys/other/nsenter.c toybox-0.8.9+dfsg/toys/other/nsenter.c
--- toybox-0.8.8+dfsg/toys/other/nsenter.c	2022-08-12 07:58:03.000000000 +0000
+++ toybox-0.8.9+dfsg/toys/other/nsenter.c	2023-01-10 19:24:45.000000000 +0000
@@ -40,7 +40,7 @@
 
     Each namespace can take an optional argument, a persistent mountpoint usable
     by the nsenter command to add new processes to that the namespace. (Specify
-    multiple namespaces to unshare seperately, ala -c -i -m because -cim is -c
+    multiple namespaces to unshare separately, ala -c -i -m because -cim is -c
     with persistent mount "im".)
 
 config NSENTER
diff -Nru toybox-0.8.8+dfsg/toys/other/openvt.c toybox-0.8.9+dfsg/toys/other/openvt.c
--- toybox-0.8.8+dfsg/toys/other/openvt.c	2022-08-12 07:58:03.000000000 +0000
+++ toybox-0.8.9+dfsg/toys/other/openvt.c	2023-01-10 19:24:45.000000000 +0000
@@ -6,7 +6,7 @@
  * No Standard
 
 USE_OPENVT(NEWTOY(openvt, "^<1c#<1>63sw", TOYFLAG_BIN|TOYFLAG_NEEDROOT))
-USE_CHVT(NEWTOY(chvt, "<1", TOYFLAG_USR|TOYFLAG_BIN))
+USE_CHVT(NEWTOY(chvt, "<1>1", TOYFLAG_USR|TOYFLAG_BIN))
 USE_DEALLOCVT(NEWTOY(deallocvt, ">1", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_NEEDROOT))
 
 config OPENVT
diff -Nru toybox-0.8.8+dfsg/toys/other/pmap.c toybox-0.8.9+dfsg/toys/other/pmap.c
--- toybox-0.8.8+dfsg/toys/other/pmap.c	2022-08-12 07:58:03.000000000 +0000
+++ toybox-0.8.9+dfsg/toys/other/pmap.c	2023-01-10 19:24:45.000000000 +0000
@@ -17,7 +17,7 @@
 
     Report the memory map of a process or processes.
 
-    -q	Show full paths
+    -p	Show full paths
     -q	Do not show header or footer
     -x	Show the extended format
 */
diff -Nru toybox-0.8.8+dfsg/toys/other/pwgen.c toybox-0.8.9+dfsg/toys/other/pwgen.c
--- toybox-0.8.8+dfsg/toys/other/pwgen.c	2022-08-12 07:58:03.000000000 +0000
+++ toybox-0.8.9+dfsg/toys/other/pwgen.c	2023-01-10 19:24:45.000000000 +0000
@@ -51,7 +51,7 @@
   for (jj = 0; jj102 less likely
       if (FLAG(s)) randbuf[rand] = 0;
 
diff -Nru toybox-0.8.8+dfsg/toys/other/readelf.c toybox-0.8.9+dfsg/toys/other/readelf.c
--- toybox-0.8.8+dfsg/toys/other/readelf.c	2022-08-12 07:58:03.000000000 +0000
+++ toybox-0.8.9+dfsg/toys/other/readelf.c	2023-01-10 19:24:45.000000000 +0000
@@ -355,7 +355,7 @@
   char *hdr = TT.elf;
   int type, machine, version, flags, entry, ehsize, phnum, shstrndx, i, j, w;
 
-  if (TT.size < 45 || memcmp(hdr, "\177ELF", 4)) 
+  if (TT.size < 45 || smemcmp(hdr, "\177ELF", 4))
     return error_msg("%s: not ELF", TT.f);
 
   TT.bits = hdr[4] - 1;
@@ -453,7 +453,7 @@
              s.entsize, sh_flags, s.link, s.info, s.addralign);
     }
   }
-  if (FLAG(S) && TT.shnum) 
+  if (FLAG(S) && TT.shnum)
     printf("Key:\n  (W)rite, (A)lloc, e(X)ecute, (M)erge, (S)trings, (I)nfo\n"
            "  (L)ink order, (O)S, (G)roup, (T)LS, (C)ompressed, x=unknown\n");
 
diff -Nru toybox-0.8.8+dfsg/toys/other/readlink.c toybox-0.8.9+dfsg/toys/other/readlink.c
--- toybox-0.8.8+dfsg/toys/other/readlink.c	2022-08-12 07:58:03.000000000 +0000
+++ toybox-0.8.9+dfsg/toys/other/readlink.c	2023-01-10 19:24:45.000000000 +0000
@@ -2,9 +2,8 @@
  *
  * Copyright 2007 Rob Landley 
 
-// -ef positions match ABS_FILE ABS_PATH
-USE_READLINK(NEWTOY(readlink, "<1nqmef(canonicalize)[-mef]", TOYFLAG_USR|TOYFLAG_BIN))
-USE_REALPATH(OLDTOY(realpath, readlink, TOYFLAG_USR|TOYFLAG_BIN))
+USE_READLINK(NEWTOY(readlink, "<1vnf(canonicalize)emqz[-mef][-qv]", TOYFLAG_USR|TOYFLAG_BIN))
+USE_REALPATH(NEWTOY(realpath, "<1(relative-base):R(relative-to):s(no-symlinks)LPemqz[-Ps][-LP][-me]", TOYFLAG_USR|TOYFLAG_BIN))
 
 config READLINK
   bool "readlink"
@@ -20,36 +19,124 @@
     -f	Full path (fail if directory missing)
     -m	Ignore missing entries, show where it would be
     -n	No trailing newline
-    -q	Quiet (no output, just error code)
+    -q	Quiet (no error messages)
+    -z	NUL instead of newline
 
 config REALPATH
   bool "realpath"
   default y
   help
-    usage: realpath FILE...
+    usage: realpath [-LPemqsz] [--relative-base DIR] [-R DIR] FILE...
 
     Display the canonical absolute pathname
+
+    -R Show ../path relative to DIR (--relative-to)
+    -L Logical path (resolve .. before symlinks)
+    -P Physical path (default)
+    -e Canonical path to existing entry (fail if missing)
+    -m Ignore missing entries, show where it would be
+    -q Quiet (no error messages)
+    -s Don't expand symlinks
+    -z NUL instead of newline
+    --relative-base  If path under DIR trim off prefix
 */
 
-#define FOR_readlink
+/* TODO
+# relative-to is affected by flags
+$ realpath --relative-to=nothing/potato .
+realpath: nothing/potato: No such file or directory
+$ realpath -m --relative-to=nothing/potato .
+../..
+
+# -L and -s are similar but not the same
+$ realpath -s --relative-to=. ccc
+ccc
+$ realpath -L --relative-to=. ccc
+../../mcm/ccc
+*/
+
+
+
+#define FOR_realpath
 #define FORCE_FLAGS
+#define TT this.readlink // workaround: first FOR_ doesn't match filename
 #include "toys.h"
 
-void readlink_main(void)
+GLOBALS(
+  char *R, *relative_base;
+)
+
+// test TT.relative_base -RsmLP
+// Trim .. out early for -s and -L. TODO: in place in the input string.
+
+static char *resolve(char *arg)
+{
+  int flags = FLAG(e) ? ABS_FILE : FLAG(m) ? 0 : ABS_PATH;
+  char *s, *ss = 0, *dd = 0;
+
+  if (FLAG(s)) flags |= ABS_KEEP;
+  else if (FLAG(L)) arg = dd = xabspath(arg, ABS_KEEP);
+  if (!(s = xabspath(arg, flags)) && !FLAG(q)) perror_msg("%s", arg);
+  free(dd);
+
+  // Trim off this prefix if path under here
+
+  if (TT.relative_base) {
+    ss = s;
+    if (strstart(&ss, TT.relative_base) && (!*ss || *ss=='/')) {
+      if (*ss=='/') ss++;
+      ss = xstrdup(!*ss ? "." : ss);
+    } else ss = 0;
+  } else if (TT.R) ss = relative_path(TT.R, s, 0);
+  if (ss) {
+    free(s);
+    s = ss;
+  }
+
+  return s;
+}
+
+// Resolve command line arguments that can't take part in their own resolution
+static char *presolve(char **s)
+{
+  char *ss = *s;
+
+  if (ss) {
+    *s = 0;
+    if (!(*s = resolve(ss))) xexit();
+  }
+
+  return ss;
+}
+
+// Uses realpath flag context: flags (1 = resolve, 2 = -n)
+static void do_paths(int flags)
 {
   char **arg, *s;
 
-  if (toys.which->name[3]=='l') toys.optflags |= FLAG_f;
+  if (!presolve(&TT.relative_base)) presolve(&TT.R);
+
   for (arg = toys.optargs; *arg; arg++) {
-    // Calculating full canonical path?
-    // Take advantage of flag positions: m = 0, f = ABS_PATH, e = ABS_FILE
-    if (toys.optflags & (FLAG_f|FLAG_e|FLAG_m))
-      s = xabspath(*arg, toys.optflags&(FLAG_f|FLAG_e));
-    else s = xreadlink(*arg);
-
-    if (s) {
-      if (!FLAG(q)) xprintf("%s%s", s, (FLAG(n) && !arg[1]) ? "" : "\n");
-      free(s);
-    } else toys.exitval = 1;
+    if (!(s = (flags&1) ? resolve(*arg) : xreadlink(*arg))) toys.exitval = 1;
+    else xprintf(((flags&2) && !arg[1]) ? "%s" : "%s%c", s, '\n'*!FLAG(z));
+    free(s);
   }
 }
+
+void realpath_main(void)
+{
+  do_paths(1);
+}
+
+#define FOR_readlink
+#include "generated/flags.h"
+
+// Convert readlink flag context to realpath (feeding in -nf separately)
+void readlink_main(void)
+{
+  int nf = (toys.optflags/FLAG_f)|!!(FLAG(m)|FLAG(e));
+
+  toys.optflags &= FLAG_f-1;
+  if (!FLAG(v)) toys.optflags |= FLAG_q;
+  do_paths(nf);
+}
diff -Nru toybox-0.8.8+dfsg/toys/other/shred.c toybox-0.8.9+dfsg/toys/other/shred.c
--- toybox-0.8.8+dfsg/toys/other/shred.c	2022-08-12 07:58:03.000000000 +0000
+++ toybox-0.8.9+dfsg/toys/other/shred.c	2023-01-10 19:24:45.000000000 +0000
@@ -90,7 +90,7 @@
       throw = sizeof(toybuf);
       if (FLAG(x) && len-pos < throw) throw = len-pos;
 
-      if (iter != TT.n) xgetrandom(toybuf, throw, 0);
+      if (iter != TT.n) xgetrandom(toybuf, throw);
       if (throw != writeall(fd, toybuf, throw)) perror_msg_raw(*try);
       pos += throw;
     }
diff -Nru toybox-0.8.8+dfsg/toys/other/swapoff.c toybox-0.8.9+dfsg/toys/other/swapoff.c
--- toybox-0.8.8+dfsg/toys/other/swapoff.c	2022-08-12 07:58:03.000000000 +0000
+++ toybox-0.8.9+dfsg/toys/other/swapoff.c	2023-01-10 19:24:45.000000000 +0000
@@ -2,20 +2,40 @@
  *
  * Copyright 2012 Elie De Brauwer 
 
-USE_SWAPOFF(NEWTOY(swapoff, "<1>1", TOYFLAG_SBIN|TOYFLAG_NEEDROOT))
+USE_SWAPOFF(NEWTOY(swapoff, "<1>1av", TOYFLAG_SBIN|TOYFLAG_NEEDROOT))
 
 config SWAPOFF
   bool "swapoff"
   default y
   help
-    usage: swapoff swapregion
+    usage: swapoff FILE
 
-    Disable swapping on a given swapregion.
+    Disable swapping on a device or file.
 */
 
+#define FOR_swapoff
 #include "toys.h"
 
+static void xswapoff(char *str)
+{
+  if (FLAG(v)) printf("swapoff %s", str);
+  if (swapoff(str)) perror_msg("failed to remove swaparea");
+}
+
 void swapoff_main(void)
 {
-  if (swapoff(toys.optargs[0])) perror_exit("failed to remove swaparea");
+  char *ss, *line, **args;
+  FILE *fp;
+
+  if (FLAG(a) && (fp = fopen("/proc/swaps", "r"))) {
+    while ((line = xgetline(fp))) {
+      if (*line != '/' || !(ss = strchr(line, ' '))) continue;
+      *ss = 0;
+      octal_deslash(line);
+      xswapoff(line);
+      free(line);
+    }
+    fclose(fp);
+  }
+  for (args = toys.optargs; *args; args++) xswapoff(*args);
 }
diff -Nru toybox-0.8.8+dfsg/toys/other/taskset.c toybox-0.8.9+dfsg/toys/other/taskset.c
--- toybox-0.8.8+dfsg/toys/other/taskset.c	2022-08-12 07:58:03.000000000 +0000
+++ toybox-0.8.9+dfsg/toys/other/taskset.c	2023-01-10 19:24:45.000000000 +0000
@@ -127,7 +127,7 @@
     char *ss;
 
     while ((de = readdir(dd))) {
-      if (memcmp(de->d_name, "cpu", 3)) continue;
+      if (smemcmp(de->d_name, "cpu", 3)) continue;
       for (ss = de->d_name+3; isdigit(*ss); ss++);
       if (!*ss) nproc++;
     }
diff -Nru toybox-0.8.8+dfsg/toys/other/timeout.c toybox-0.8.9+dfsg/toys/other/timeout.c
--- toybox-0.8.8+dfsg/toys/other/timeout.c	2022-08-12 07:58:03.000000000 +0000
+++ toybox-0.8.9+dfsg/toys/other/timeout.c	2023-01-10 19:24:45.000000000 +0000
@@ -34,6 +34,7 @@
 
   struct pollfd pfd;
   sigjmp_buf sj;
+  int fds[2], pid;
 )
 
 static void handler(int sig)
@@ -46,29 +47,35 @@
   return ts->tv_sec*1000+ts->tv_nsec/1000000;
 }
 
+static void callback(char *argv[])
+{
+  xsignal(SIGCHLD, SIG_DFL);
+  if (!FLAG(foreground)) setpgid(0, 0);
+}
+
 void timeout_main(void)
 {
-  int fds[] = {0, -1}, ii, ms, nextsig, pid;
+  int ii, ms, nextsig = SIGTERM;
   struct timespec tts, kts;
 
   // Use same ARGFAIL value for any remaining parsing errors
   toys.exitval = 125;
   xparsetimespec(*toys.optargs, &tts);
   if (TT.k) xparsetimespec(TT.k, &kts);
-
-  nextsig = SIGTERM;
-  if (TT.s && -1 == (nextsig = sig_to_num(TT.s)))
-    error_exit("bad -s: '%s'", TT.s);
-
-  if (!FLAG(foreground)) setpgid(0, 0);
+  if (TT.s && -1==(nextsig = sig_to_num(TT.s))) error_exit("bad -s: '%s'",TT.s);
 
   toys.exitval = 0;
   TT.pfd.events = POLLIN;
+  TT.fds[1] = -1;
   if (sigsetjmp(TT.sj, 1)) goto done;
   xsignal_flags(SIGCHLD, handler, SA_NOCLDSTOP);
-  pid = xpopen_both(toys.optargs+1, FLAG(i) ? fds : 0);
-  if (!FLAG(i)) xpipe(fds);
-  TT.pfd.fd = fds[1];
+
+  TT.pid = xpopen_setup(toys.optargs+1, FLAG(i) ? TT.fds : 0, callback);
+  xsignal(SIGTTIN, SIG_IGN);
+  xsignal(SIGTTOU, SIG_IGN);
+  xsignal(SIGTSTP, SIG_IGN);
+  if (!FLAG(i)) xpipe(TT.fds);
+  TT.pfd.fd = TT.fds[1];
   ms = nantomil(&tts);
   for (;;) {
     if (1 != xpoll(&TT.pfd, 1, ms)) {
@@ -76,7 +83,7 @@
         perror_msg("sending signal %s to command %s", num_to_sig(nextsig),
           toys.optargs[1]);
       toys.exitval = (nextsig==9) ? 137 : 124;
-      kill(pid, nextsig);
+      kill(FLAG(foreground) ? TT.pid : -TT.pid, nextsig);
       if (!TT.k || nextsig==SIGKILL) break;
       nextsig = SIGKILL;
       ms = nantomil(&kts);
@@ -85,7 +92,7 @@
     }
     if (TT.pfd.revents&POLLIN) {
       errno = 0;
-      if (1>(ii = read(fds[1], toybuf, sizeof(toybuf)))) {
+      if (1>(ii = read(TT.fds[1], toybuf, sizeof(toybuf)))) {
         if (errno==EINTR) continue;
         break;
       }
@@ -95,7 +102,7 @@
   }
 done:
   xsignal(SIGCHLD, SIG_DFL);
-  ii = xpclose_both(pid, fds);
+  ii = xpclose_both(TT.pid, TT.fds);
 
   if (FLAG(preserve_status) || !toys.exitval) toys.exitval = ii;
 }
diff -Nru toybox-0.8.8+dfsg/toys/other/usleep.c toybox-0.8.9+dfsg/toys/other/usleep.c
--- toybox-0.8.8+dfsg/toys/other/usleep.c	2022-08-12 07:58:03.000000000 +0000
+++ toybox-0.8.9+dfsg/toys/other/usleep.c	2023-01-10 19:24:45.000000000 +0000
@@ -2,7 +2,7 @@
  *
  * Copyright 2012 Elie De Brauwer 
 
-USE_USLEEP(NEWTOY(usleep, "<1", TOYFLAG_BIN))
+USE_USLEEP(NEWTOY(usleep, "<1>1", TOYFLAG_BIN))
 
 config USLEEP
   bool "usleep"
diff -Nru toybox-0.8.8+dfsg/toys/other/watchdog.c toybox-0.8.9+dfsg/toys/other/watchdog.c
--- toybox-0.8.8+dfsg/toys/other/watchdog.c	2022-08-12 07:58:03.000000000 +0000
+++ toybox-0.8.9+dfsg/toys/other/watchdog.c	2023-01-10 19:24:45.000000000 +0000
@@ -4,7 +4,7 @@
  *
  * See kernel.org/doc/Documentation/watchdog/watchdog-api.txt
 
-USE_WATCHDOG(NEWTOY(watchdog, "<1>1Ft#=4<1T#=60<1", TOYFLAG_NEEDROOT|TOYFLAG_BIN))
+USE_WATCHDOG(NEWTOY(watchdog, "<1>1Ft#=4<1T#=60<1", TOYFLAG_NEEDROOT|TOYFLAG_SBIN))
 
 config WATCHDOG
   bool "watchdog"
diff -Nru toybox-0.8.8+dfsg/toys/other/xxd.c toybox-0.8.9+dfsg/toys/other/xxd.c
--- toybox-0.8.8+dfsg/toys/other/xxd.c	2022-08-12 07:58:03.000000000 +0000
+++ toybox-0.8.9+dfsg/toys/other/xxd.c	2023-01-10 19:24:45.000000000 +0000
@@ -10,7 +10,7 @@
  * xxd -p "plain" output:
  *   "4c696e75782076657273696f6e20342e392e302d342d616d643634202864"
 
-USE_XXD(NEWTOY(xxd, ">1c#<0>256l#o#g#<1=2iprs#[!rs]", TOYFLAG_USR|TOYFLAG_BIN))
+USE_XXD(NEWTOY(xxd, ">1c#<0>256l#o#g#<0=2iprs#[!rs]", TOYFLAG_USR|TOYFLAG_BIN))
 
 config XXD
   bool "xxd"
@@ -26,7 +26,7 @@
     -i	Output include file (CSV hex bytes, plus C header/footer if not stdin)
     -l n	Limit of n bytes before stopping (default is no limit)
     -o n	Add n to display offset
-    -p	Plain hexdump (30 bytes/line, no grouping)
+    -p	Plain hexdump (30 bytes/line, no grouping. With -c 0 no wrap/group)
     -r	Reverse operation: turn a hexdump into a binary file
     -s n	Skip to offset n
 */
@@ -42,7 +42,7 @@
 {
   long long pos = 0;
   long long limit = TT.l;
-  int i, len, space;
+  int i, len, space, c = TT.c ? : sizeof(toybuf);
 
   if (FLAG(s)) {
     xlseek(fd, TT.s, SEEK_SET);
@@ -51,15 +51,16 @@
   }
 
   while (0<(len = readall(fd, toybuf,
-                          (limit && limit-pos=' ' && toybuf[i]<='~') ? toybuf[i] : '.');
     }
-    putchar('\n');
+    if (TT.c || !FLAG(p)) putchar('\n');
   }
+  if (!TT.c && FLAG(p)) putchar('\n');
   if (len<0) perror_exit("read");
 }
 
@@ -105,7 +107,7 @@
 static void do_xxd_reverse(int fd, char *name)
 {
   FILE *fp = xfdopen(xdup(fd), "r");
-  long long current_pos = 0;
+  long long pos, current_pos = 0;
   int tmp;
 
   // -ri is a very easy special case.
@@ -115,21 +117,17 @@
 
     // Each line of a regular hexdump starts with an offset/address.
     // Each line of a plain hexdump just goes straight into the bytes.
-    if (!FLAG(p)) {
-      long long pos;
-
-      if (fscanf(fp, "%llx: ", &pos) == 1) {
-        if (pos != current_pos && fseek(stdout, pos, SEEK_SET) != 0) {
-          // TODO: just write out zeros if non-seekable?
-          perror_exit("%s: seek failed", name);
-        }
+    if (!FLAG(p) && fscanf(fp, "%llx: ", &pos) == 1) {
+      if (pos != current_pos && fseek(stdout, pos, SEEK_SET)) {
+        // TODO: just write out zeros if non-seekable?
+        perror_exit("%s: seek failed", name);
       }
     }
 
     // A plain hexdump can have as many bytes per line as you like,
     // but a non-plain hexdump assumes garbage after it's seen the
     // specified number of bytes.
-    while (FLAG(p) || col < TT.c) {
+    while (FLAG(p) || !TT.c || col < TT.c) {
       int n1, n2;
 
       // If we're at EOF or EOL or we read some non-hex...
@@ -159,10 +157,9 @@
 
 void xxd_main(void)
 {
-  if (!TT.c) TT.c = FLAG(i) ? 12 : 16;
-
   // Plain style is 30 bytes/line, no grouping.
-  if (FLAG(p)) TT.c = TT.g = 30;
+  if (!FLAG(c)) TT.c = FLAG(p) ? 30 : FLAG(i) ? 12 : 16;
+  if (FLAG(p) && !FLAG(g)) TT.g = TT.c;
 
   loopfiles(toys.optargs,
     FLAG(r) ? do_xxd_reverse : (FLAG(i) ? do_xxd_include : do_xxd));
diff -Nru toybox-0.8.8+dfsg/toys/pending/diff.c toybox-0.8.9+dfsg/toys/pending/diff.c
--- toybox-0.8.8+dfsg/toys/pending/diff.c	2022-08-12 07:58:03.000000000 +0000
+++ toybox-0.8.9+dfsg/toys/pending/diff.c	2023-01-10 19:24:45.000000000 +0000
@@ -8,18 +8,19 @@
  *
  * Deviations from posix: always does -u
 
-USE_DIFF(NEWTOY(diff, "<2>2(unchanged-line-format):;(old-line-format):;(new-line-format):;(color)(strip-trailing-cr)B(ignore-blank-lines)d(minimal)b(ignore-space-change)ut(expand-tabs)w(ignore-all-space)i(ignore-case)T(initial-tab)s(report-identical-files)q(brief)a(text)S(starting-file):L(label)*N(new-file)r(recursive)U(unified)#<0=3", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_ARGFAIL(2)))
+USE_DIFF(NEWTOY(diff, "<2>2(unchanged-line-format):;(old-line-format):;(new-line-format):;(color)(strip-trailing-cr)B(ignore-blank-lines)d(minimal)b(ignore-space-change)ut(expand-tabs)w(ignore-all-space)i(ignore-case)T(initial-tab)s(report-identical-files)q(brief)a(text)S(starting-file):F(show-function-line):;L(label)*N(new-file)r(recursive)U(unified)#<0=3", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_ARGFAIL(2)))
 
 config DIFF
   bool "diff"
   default n
   help
-  usage: diff [-abBdiNqrTstw] [-L LABEL] [-S FILE] [-U LINES] FILE1 FILE2
+  usage: diff [-abBdiNqrTstw] [-L LABEL] [-S FILE] [-U LINES] [-F REGEX ] FILE1 FILE2
 
   -a	Treat all files as text
   -b	Ignore changes in the amount of whitespace
   -B	Ignore changes whose lines are all blank
   -d	Try hard to find a smaller set of changes
+  -F 	Show the most recent line matching the regex
   -i	Ignore case differences
   -L	Use LABEL instead of the filename in the unified header
   -N	Treat absent files as empty
@@ -48,7 +49,7 @@
 GLOBALS(
   long U;
   struct arg_list *L;
-  char *S, *new_line_format, *old_line_format, *unchanged_line_format;
+  char *F, *S, *new_line_format, *old_line_format, *unchanged_line_format;
 
   int dir_num, size, is_binary, differ, change, len[2], *offset[2];
   struct stat st[2];
@@ -383,6 +384,33 @@
   return create_j_vector();
 }
 
+static void print_line_matching_regex(int a, regex_t *reg, int *off_set, FILE *fp) {
+  int i = 0, j = 0, line_buf_size = 100, cc = 0;
+  char* line = xzalloc(line_buf_size * sizeof(char));
+  for (i = a; a > 0; --i) {
+    int line_len = 0;
+    if (fseek(fp, off_set[i - 1], SEEK_SET)) perror_exit("fseek failed");
+    for (j = 0; j < (off_set[i] - off_set[i - 1]); j++) {
+      cc = fgetc(fp);
+      if (cc == EOF || cc == '\n') {
+        break;
+      }
+      ++line_len;
+      if (line_len >= line_buf_size) {
+        line_buf_size = line_buf_size * 11 / 10;
+        line = xrealloc(line, line_buf_size*sizeof(char));
+      }
+      line[j] = cc;
+    }
+    line[line_len] = '\0';
+    if (!regexec0(reg, line, line_len, 0, NULL, 0)) {
+      printf(" %s", line);
+      break;
+    }
+  }
+  free(line);
+}
+
 static void print_diff(int a, int b, char c, int *off_set, FILE *fp)
 {
   int i, j, cc, cl;
@@ -557,12 +585,17 @@
   struct diff *d;
   struct arg_list *llist = TT.L;
   int *J;
+  regex_t reg;
   
   TT.offset[0] = TT.offset[1] = NULL;
   J = diff(files);
 
   if (!J) return; //No need to compare, have to status only
 
+  if (TT.F) {
+    xregcomp(®, TT.F, 0);
+  }
+
   d = xzalloc(size *sizeof(struct diff));
   do {
     ignore_white = 0;
@@ -656,6 +689,9 @@
         else putchar(' ');
         printf("@@");
         if (FLAG(color)) printf("\e[0m");
+        if (TT.F) {
+          print_line_matching_regex(ptr1->suff-1, ®, TT.offset[0], TT.file[0].fp);
+        }
         putchar('\n');
       }
 
diff -Nru toybox-0.8.8+dfsg/toys/pending/getopt.c toybox-0.8.9+dfsg/toys/pending/getopt.c
--- toybox-0.8.8+dfsg/toys/pending/getopt.c	2022-08-12 07:58:03.000000000 +0000
+++ toybox-0.8.9+dfsg/toys/pending/getopt.c	2023-01-10 19:24:45.000000000 +0000
@@ -22,6 +22,7 @@
 
 #define FOR_getopt
 #include "toys.h"
+#include  // Everything else uses lib/args.c
 
 GLOBALS(
   struct arg_list *l;
diff -Nru toybox-0.8.8+dfsg/toys/pending/git.c toybox-0.8.9+dfsg/toys/pending/git.c
--- toybox-0.8.8+dfsg/toys/pending/git.c	1970-01-01 00:00:00.000000000 +0000
+++ toybox-0.8.9+dfsg/toys/pending/git.c	2023-01-10 19:24:45.000000000 +0000
@@ -0,0 +1,658 @@
+/* git.c - A minimal git clone
+ *
+ * Copyright 2022 Moritz C. Weber 
+ *
+ * See https://git-scm.com/docs/git-init
+ * https://git-scm.com/docs/git-remote
+ * https://git-scm.com/docs/git-fetch
+ * https://git-scm.com/docs/git-checkout
+ * https://git-scm.com/docs/pack-format
+ * https://git-scm.com/docs/index-format
+ * https://www.alibabacloud.com/blog/a-detailed-explanation-of-the-underlying-data-structures-and-principles-of-git_597391
+ * https://github.com/git/git/blob/master/Documentation/gitformat-pack.txt
+ * https://stackoverflow.com/a/14303988
+ * https://stackoverflow.com/a/21599232
+ * https://github.com/tarruda/node-git-core/blob/master/src/js/delta.js
+
+
+USE_GITCLONE(NEWTOY(gitclone, "<1", TOYFLAG_USR|TOYFLAG_BIN))
+USE_GITINIT(NEWTOY(gitinit, "<1", TOYFLAG_USR|TOYFLAG_BIN))
+USE_GITREMOTE(NEWTOY(gitremote, "<1", TOYFLAG_USR|TOYFLAG_BIN))
+USE_GITFETCH(NEWTOY(gitfetch, 0, TOYFLAG_USR|TOYFLAG_BIN))
+USE_GITCHECKOUT(NEWTOY(gitcheckout, "<1", TOYFLAG_USR|TOYFLAG_BIN))
+
+config GITCOMPAT
+  bool "gitcompat"
+  default n
+  help
+    Enable git compatible repos instead of minimal clone downloader.
+
+config GITCLONE
+  bool "gitclone"
+  default n
+  help
+    usage: gitclone URL
+    A minimal git clone.
+
+config GITINIT
+  bool "gitinit"
+  default n
+  help
+    usage: gitinit NAME
+    A minimal git init.
+
+config GITREMOTE
+  bool "gitremote"
+  default n
+  help
+    usage: gitremote URL
+    A minimal git remote add origin.
+
+config GITFETCH
+  bool "gitfetch"
+  default n
+  help
+    usage: gitfetch
+    A minimal git fetch.
+
+config GITCHECKOUT
+  bool "gitcheckout"
+  default n
+  help
+    usage: gitcheckout 
+    A minimal git checkout.
+*/
+
+#define TT  this.git
+#define FOR_gitclone
+#include "toys.h"
+#include "openssl/sha.h" //ToDo: borrowed from OpenSSL to not pipe or refactor the SHA1SUM in toybox
+#include "zlib.h"  //ToDo: borrowed from libz to not refactor deflate.c
+
+GLOBALS(
+  char *url, *name; //git repo remote url and init directory name
+  struct IndexV2 *i; //git creates a index for each pack file, git clone just needs one index for the received pack file
+)
+
+//git index format v2 described at https://github.com/git/git/blob/master/Documentation/gitformat-pack.txt#L266
+struct IndexV2 {
+  char header[8];// Git 4 byte magic number and 4 byte version number
+  unsigned fot[256];//A fan-out table
+  char (*sha1)[20];//Table of sorted object names(SHA1 hashes)
+  unsigned *crc, *offset;//Table of 4-bit CRC32 values and object offsets in pack file
+  long long *offset64; //8 byte offests -- not supported yet
+  char packsha1[20], idxsha1[20];//SHA1 hash of pack file and SHA1 hash of index file
+};
+
+//TODO:This function is not used before git clone persists an index V2
+static void read_index(struct IndexV2 *i)
+{
+  FILE *fpi;
+
+  i = malloc(sizeof(i));
+  i->sha1 = malloc(20*sizeof(char));
+  i->crc = malloc(sizeof(unsigned));
+  i->offset = malloc(sizeof(unsigned));
+  i->offset64 = malloc(sizeof(long long));
+  //TODO: not used yet as index is not persisted yet
+  if (access(".git/object/pack/temp.idx", F_OK)==0) {
+    //persistance needed for other git commands (not clone)
+    fpi = fopen(".git/object/pack/temp.idx", "rb");
+    printf("read header\n");
+    fread(i->header, sizeof(i->header), 1, fpi);
+    printf("Header: %s..Read fot\n", i->header);
+    fread(i->fot, 4, 256, fpi);
+    printf("Elements %d..Read sha1\n", i->fot[255]);
+    fread(i->sha1, sizeof(i->fot), i->fot[255], fpi);
+    printf("read crc\n");
+    fread(i->crc, sizeof(i->fot), i->fot[255], fpi);
+    printf("read offset\n");
+    fread(i->offset, sizeof(i->fot), i->fot[255], fpi);
+    //TODO: Offsets for file size 2G missing here
+    printf("read packsha\n");
+    fread(i->packsha1, 20, 1, fpi);
+    printf("read idxsha\n");
+    fread(i->idxsha1, 20, 1, fpi);
+    fclose(fpi);
+  }
+}
+
+long bsearchpos(const void *k, const void *a, size_t h, size_t w)
+{
+  long l = 0, m = 0, r = 0;
+
+  if (!h) return 0;
+  while (h>0) {
+    m = l+(h/2);
+    r = strncmp(k, a+(m*w), 20);
+    if (!r||h==1) break; //match on search or position for insert
+    if (r<0) { h /= 2; } else { l = m; h -= h/2; }
+  }
+
+  //For inserts check if insert is bigger  obj at identified position
+  return m += (r>0) ? 1 : 0;
+}
+
+//find offset position in packfile for given SHA1 hash
+long get_index(struct IndexV2 *i, char *h)
+{
+  long pos = bsearchpos(h, i->sha1[0], i->fot[255], 20);
+ return i->offset[pos];
+}
+
+//https://github.com/git/git/blob/master/Documentation/gitformat-pack.txt#L35
+//https://yqintl.alicdn.com/eef7fe4f22cc97912cee011c99d3fe5821ae9e88.png
+//read type and length of an packed object at a given offset
+unsigned long long unpack(FILE *fpp, int *type, long *offset)
+{
+  int bitshift= 4;
+  unsigned long long length = 0;
+  char data;
+
+  printf("Start unpack\n");
+  fseek(fpp, *offset, SEEK_SET);
+  printf("Offset set to: %ld\n", *offset);
+  fread(&data, 1, 1, fpp);
+  printf("Data: %d\n", data);
+  *type = ((data & 0x70)>>4);
+  printf("Type: %d\n", *type);
+  length |= data & 0x0F;
+  while ((data & 0x80) && fread(&data, 1, 1, fpp)!=-1)
+  {
+    length |= (unsigned long long)(data & 0x7F) << bitshift;
+    bitshift += 7; // (*offset)++;
+  }
+  printf("Length: %llu\n", length);
+
+  return length;
+}
+
+//   ToDo: borrowed from int inf(FILE *source, FILE *dest) in
+//   zpipe.c: example of proper use of zlib's inflate() and deflate()
+//   Not copyrighted -- provided to the public domain
+//   Version 1.4  11 December 2005  Mark Adler */
+#define CHUNK 4096
+int inf(FILE *source, char *dest) //modified signature to ease use
+{
+    int ret;
+    char *position = dest;
+    unsigned have;
+    z_stream strm;
+    unsigned char in[CHUNK];
+    unsigned char out[CHUNK];
+    strm.zalloc = Z_NULL;
+    strm.zfree = Z_NULL;
+    strm.opaque = Z_NULL;
+    strm.avail_in = 0;
+    strm.next_in = Z_NULL;
+    ret = inflateInit(&strm);
+    if (ret != Z_OK) return ret;
+
+    // decompress until deflate stream ends or end of file
+    do {
+
+        strm.avail_in = fread(in, 1, CHUNK, source);
+        if (ferror(source)) {
+            (void)inflateEnd(&strm);
+
+            return Z_ERRNO;
+        }
+        if (strm.avail_in == 0)
+            break;
+        strm.next_in = in;
+
+
+        // run inflate() on input until output buffer not full
+        do {
+
+            strm.avail_out = CHUNK;
+            strm.next_out = out;
+
+            ret = inflate(&strm, Z_NO_FLUSH);
+            //assert(ret != Z_STREAM_ERROR);  // state not clobbered
+            switch (ret) {
+            case Z_NEED_DICT:
+                ret = Z_DATA_ERROR;     // and fall through
+            case Z_DATA_ERROR:
+            case Z_MEM_ERROR:
+                (void)inflateEnd(&strm);
+
+                return ret;
+            }
+
+            have = CHUNK - strm.avail_out;
+	    memcpy(position, out, have); //added to original
+            position += have; //added to original
+            //if (fwrite(out, 1, have, dest) != have || ferror(dest)) {
+            //    (void)inflateEnd(&strm);
+            //    return Z_ERRNO;
+            //}
+        } while (strm.avail_out == 0);
+       // done when inflate() says it's done
+    } while (ret != Z_STREAM_END);
+    // modified from zpipe.c to set FP to end of zlib object
+    fseek(source, ftell(source)-strm.avail_in, SEEK_SET);
+    // clean up and return
+    (void)inflateEnd(&strm);
+
+    return ret == Z_STREAM_END ? Z_OK : Z_DATA_ERROR;
+}
+
+//https://github.com/git/git/blob/master/Documentation/gitformat-pack.txt#L72
+//Set object to the index after adding prefix, calculating the hash and finding the position
+long set_object(struct IndexV2 *idx, int type, char *o, unsigned count,
+  unsigned ofs)
+{
+// TODO: Too many allocs in here 1) to concat the search string for hashing
+// 2) to insert into the array (can be reduce to a single malloc in fetch as
+// the pack header contains the number of objects in pack
+  char *c, *p = "", *h = (char*)xmalloc(sizeof(char)*20); //composition,prefix,hash
+  long pos = 0;
+
+  printf("Alloc... ");
+  //append a object prefix based on its type (not included in packfile)
+  switch(type) {
+    case 1: p = xmprintf("commit %d", count); break; //count is used as o can contain \0 in the  string
+    case 2: p = xmprintf("tree %d", count); break;
+    case 3: p = xmprintf("blob %d", count); break;
+    case 4: p = xmprintf("tag %d", count); break;
+    case 6: printf("REF_DELTA"); break; //not expected in fetch packs as fetch packs are self-containing
+    case 7: printf("OBJ_DELTA\n"); break;
+  }
+  c = (char*)xmalloc(strlen(p)+count+2); //Robs null terminator embedding
+  if (c == NULL) error_exit("c malloc failed in set_object");
+  memcpy(c, p, strlen(p)+1); //Robs null terminator embedding
+  memcpy(c+strlen(p)+1, o, count+1); //Robs null terminator embedding
+  h = SHA1(c, strlen(p)+count+1, h); //ToDo: borrowed from OpenSSL to not to pipe or refactor SHA1SUM in toybox
+  printf("..Binary search\n");
+  for (int j = 0; j<20; j++) printf("%02x", h[j]); //find insert position
+  pos = bsearchpos(h, idx->sha1[0], idx->fot[255], 20);
+  printf("\n..Insert pos %ld\n", pos);
+  printf("..Preloop\n");
+
+  //adjust of fanout table https://github.com/git/git/blob/master/Documentation/gitformat-pack.txt#L204
+  for (int i = h[0]; i<=255; i++) idx->fot[i] += 1;
+  printf("Post loop\n");
+  printf("Resize sha1 array..idx->fot[255]%d\n", idx->fot[255]); //Memory management for insert
+  //TODO:Could be also a single malloc at gitfetch based on the nbr of objects in pack
+
+  //Did not fix the TODO yet, because set_object could be reused for other commands adding single objects to the index
+  idx->sha1 = realloc(idx->sha1, (idx->fot[255]+1)*20*sizeof(char));
+  printf("Mem copy sha1 array..sizeof(idx->sha1)%ld\n", sizeof(idx->sha1));
+  memmove(&idx->sha1[pos+1], &idx->sha1[pos], (idx->fot[255]-pos)*20*sizeof(char));
+  printf("Resize offset\n");
+  idx->offset = realloc(idx->offset, (idx->fot[255]+1)*sizeof(unsigned));
+  printf("Mem copy offset\n");
+  memmove(&idx->offset[pos+1], &idx->offset[pos], sizeof(unsigned)*(idx->fot[255]-pos));
+  printf("Set offset value\n");
+  memcpy(&idx->sha1[pos], h, 20); //insert SHA1
+  idx->offset[pos] = ofs; //insert offset of SHA1
+  //ToDo: id->crc[idx->fot[h[0]]]=;
+  printf("Write object\n");
+  free(h);
+  free(c);
+
+  return ofs;
+}
+
+//init a git repository in a given directory name
+static void gitinit(char *name)
+{
+  //For git clone actually only refs and object/pack are needed
+  if (mkdir(name, 0755)!=0){
+    //I create the others for a git compliant folder structure
+    mkdir(xmprintf("%s%s", name, "/.git"), 0755);
+    mkdir(xmprintf("%s%s", name, "/.git/objects"), 0755);
+    mkdir(xmprintf("%s%s", name, "/.git/objects/pack"), 0755);
+    mkdir(xmprintf("%s%s", name, "/.git/branches"), 0755);
+    mkdir(xmprintf("%s%s", name, "/.git/hooks"), 0755); //hook files skipped as implementations does not support hooks
+    mkdir(xmprintf("%s%s", name, "/.git/info"), 0755);
+    mkdir(xmprintf("%s%s", name, "/.git/objects/info"), 0755);
+    mkdir(xmprintf("%s%s", name, "/.git/refs"), 0755);
+    mkdir(xmprintf("%s%s", name, "/.git/heads"), 0755);
+    mkdir(xmprintf("%s%s", name, "/.git/tags"), 0755);
+    xcreate(xmprintf("%s%s", name, "/.git/config"), O_CREAT, 0644);
+    xcreate(xmprintf("%s%s", name, "/.git/description"), O_CREAT, 0644);
+    xcreate(xmprintf("%s%s", name, "/.git/HEAD"), O_CREAT, 0644);
+    xcreate(xmprintf("%s%s", name, "/.git/info/exclude"), O_CREAT, 0644);
+  }
+}
+
+//set basic configuration and add remote URL
+static void gitremote(char *url)
+{
+  if (access(".git/config", F_OK)!=0) {
+    FILE *fp = fopen(".git/config", "wb");
+
+    fwrite("[core]\n", 1, 7, fp);
+    fwrite("\trepositoryformatversion = 0\n", 1, 29, fp);
+    fwrite("\tfilemode = false\n", 1, 18, fp);
+    fwrite("\tbare = false\n", 1, 14, fp);
+    fwrite("\tlogallrefupdates = true\n", 1, 25, fp);
+    fwrite("\tsymlinks = false\n", 1, 18, fp);
+    fwrite("\tignorecase = true\n", 1, 19, fp);
+    fwrite("[remote \"origin\"]\n", 1, 18, fp);
+    fwrite(xmprintf("\turl = %s/refs\n", TT.url), 1, strlen(TT.url)+13, fp);
+    fwrite("\tfetch = +ref/heads/*:refs/remotes/origin/*\n", 1, 44, fp);
+    fclose(fp);
+  }
+}
+
+// this is most likely still buggy and create a late observable heap overflow larger deltafied repos
+// https://stackoverflow.com/a/14303988
+// resolve deltafied objects in the pack file, see URL in comments for further explainations
+char *resolve_delta(char *s, char *d, long dsize, unsigned *count)
+{
+  long pos = 0, bitshift = 0;
+  //https://github.com/git/git/blob/master/Documentation/gitformat-pack.txt#L113
+  // Skipping source size; did not find out why it is  on the delta header as the source object header contains it too; maybe misunderstood and this makes things buggy, but I dont need it here
+  while ((d[pos] & 0x80)) pos++;
+  pos++; //fixes https://github.com/git/git/blob/master/Documentation/gitformat-pack.txt#L114
+  *count = 0;
+  bitshift = 0;
+  while ((d[pos] & 0x80)) { //reading target_size from header
+    *count |= (unsigned long long)(d[pos++]& 0x7F) << bitshift;
+    bitshift += 7; // (*offset)++;
+  }
+
+  *count |= (unsigned long long)(d[pos++]& 0x7F) << bitshift;
+  printf("Target Count %d:\n", *count);
+  char *t = malloc(sizeof(char)*(*count+1));
+  if (t == NULL) error_exit("t malloc failed in resolve_delta");
+  *count = 0;
+  while (pos0) ? xmprintf("%s/%s", path, object+pos+7) : object+pos+7;
+        printf("prepare file %s\n", name);
+      } else { //tree object reference is a folder
+        // concat folder name
+        name = (strlen(path)>0) ? xmprintf("%s/%s", path, object+pos+6) : object+pos+6;
+        printf("create folder %s\n", name);
+        mkdir(name, 0755); //TODO: umask
+      }
+      memcpy(hash, hs, 20);
+      write_children(hash, name, fpp);
+      pos = hs-object+20;
+      printf("Position/count for %s: %d/%u\n", path, pos, count);
+    }
+    printf("**EXIT WHILE**\n");
+  } else { //at blob/file object
+    printf("process file %s\n", path);
+    fc = fopen(path, "w");
+    printf("process opened \n");
+    fputs(object, fc); //TODO:Not sure if length might be an issue here
+    printf("process file written\n");
+    fclose(fc);
+  }
+  free(object);
+  printf("Child: %s done\n", path);
+}
+
+//fetches the meta data from the remote repository,requests a pack file for the remote master head,
+//unpacks all objects and set objects to the index
+static void gitfetch(void)
+{
+  printf("refs\n");
+  pid_t pid;
+
+  // TODO:I use herein after two temp files for fetch which git does not offer
+  // to 1) avoid a rewrite and 2) messing up the repo files while testing
+
+  // TODO: Refactor wget into lib
+  if ((pid = fork())==0)
+    execv("toybox", (char *[]){"toybox", "wget", "-O", ".git/refs/temp.refs",
+      "https://github.com/landley/toybox/info/refs?service=git-upload-pack",
+      (char*)0});
+  perror("execv\n");
+  //char h[] = "8cf1722f0fde510ea81d13b31bde1e48917a0306";
+  //TODO: Replace static testing hash and uncomment the following line if rare delta resolve /?heap overflow? bug was found
+  FILE *fpr = fopen(".git/ref/temp.refs", "r");
+  char *h;size_t l =0;
+  getline(&h,&l,fpr);
+  getline(&h,&l,fpr);
+  getline(&h,&l,fpr);
+  getline(&h,&l,fpr);
+  fclose(fpr);
+  strcpy(h,&h[4]);
+  h[40]='\0';
+  printf("Master HEAD hash: %s\n",h);
+  //TODO: Persist hash to /refs/master/HEAD
+  printf("pack\n");
+  if ((pid = fork())==0) execv("toybox", (char *[]){"toybox", "wget", "-O", ".git/objects/pack/temp.pack", "-p", xmprintf("$'0032want %s\n00000009done\n'", h), "https://github.com/landley/toybox/git-upload-pack", (char*)0}); //TODO: does not skip 0008NAK  printf("init\n");
+  perror("execv\n");
+  FILE *fpp;
+  printf("openpack\n");
+  fpp = fopen(".git/objects/pack/temp.pack", "r");
+  printf("read index\n");
+  read_index(TT.i); //init index with out reading
+  printf("init\n");
+  unsigned ocount = 0, count;
+  int type;
+  char *object;
+
+  printf("skip header\n");
+  long offset = 12+8; //8byte from the wget post response are skipped too
+  fseek(fpp, 8+8, SEEK_SET); //header check skipped //header check skipped https://github.com/git/git/blob/master/Documentation/gitformat-pack.txt#L37
+  printf("read count\n");
+  fread(&ocount, 4, 1, fpp);
+  ocount = ntohl(ocount); //https://github.com/git/git/blob/master/Documentation/gitformat-pack.txt#L46
+  printf("Count: %d ..Loop pack\n", ocount);
+  for (int j = 0; j/head
+static void gitcheckout(char *name)
+{
+
+  FILE *fpp;
+  //FILE *fh;
+  printf("Find branch for checkout\n");
+  //fh = fopen(xmprintf(".git/ref/heads/%s", name ? "master" : name), "r"); //TODO: Checkout master as in ref/heads
+  printf("Read head\n");
+  //hf[fread(&hf, 40, 1, fh)] = '\0';
+  //fclose(fh);
+  printf("Close heads and read pack\n");
+  fpp = fopen(".git/objects/pack/temp.pack", "r");
+  printf("set signature\n");
+  char *p = "52fb04274b3491fdfe91b2e5acc23dc3f3064a86"; //static hashes for testing toybox 0.0.1";
+  //char *p = "c555a0ca46e75097596274bf5e634127015aa144"; //static hashes for testing 0.0.2";
+  //char *p = "4307a7b07cec4ad8cbab47a29ba941f8cb041812"; //static hashes for testing 0.0.3";
+  //char *p = "3632d5d8fe05d14da983e37c7cd34db0769e6238"; //static hashes for testing 0.0.4";
+  //char *p = "8cf1722f0fde510ea81d13b31bde1e48917a0306"; //3604ba4f42c3d83e2b14f6d0f423a33a3a8706c3";
+  printf("enter tree root\n");
+  write_children(txtoh(p), "", fpp);
+  fclose(fpp);
+}
+
+void gitclone_main(void)
+{
+  TT.url = xstrdup(toys.optargs[0]);
+  if (strend(TT.url, ".git")) TT.url[strlen(TT.url)-4] = '\0';
+  TT.name = strrchr(TT.url, '/')+1;
+  gitinit(TT.name);
+  chdir(TT.name);
+  TT.i = malloc(sizeof(struct IndexV2));
+  gitremote(TT.url);
+  gitfetch();
+  gitcheckout("master");
+  chdir("..");
+}
+
+#define FOR_gitinit
+#include "generated/flags.h"
+
+void gitinit_main(void)
+{
+  gitinit(xstrdup(toys.optargs[0]));
+}
+
+#define FOR_gitremote
+
+void gitremote_main(void)
+{
+  TT.url = xstrdup(toys.optargs[0]);
+  if (strend(TT.url, ".git")) TT.url[strlen(TT.url)-4] = '\0';
+  gitremote(TT.url);
+}
+
+#define FOR_gitinit
+
+void gitfetch_main(void)
+{
+  gitfetch();
+}
+
+#define FOR_gitcheckout
+
+void gitcheckout_main(void)
+{
+  gitcheckout(xstrdup(toys.optargs[0]));
+}
+
+// Command to wget refs and pack file using toybox wget to place them manually into the repository
+// ./toybox wget -O - -d https://github.com/landley/toybox/info/refs?service=git-upload-pack | less
+
+// ./toybox wget -O pack.dat -d -p $'0032want 8cf1722f0fde510ea81d13b31bde1e48917a0306\n00000009done\n' https://github.com/landley/toybox/git-upload-pack
diff -Nru toybox-0.8.8+dfsg/toys/pending/modprobe.c toybox-0.8.9+dfsg/toys/pending/modprobe.c
--- toybox-0.8.8+dfsg/toys/pending/modprobe.c	2022-08-12 07:58:03.000000000 +0000
+++ toybox-0.8.9+dfsg/toys/pending/modprobe.c	2023-01-10 19:24:45.000000000 +0000
@@ -444,7 +444,7 @@
     }
     // none of above is true insert the module.
     errno = 0;
-    rc = ins_mod(fn, options);
+    rc = ins_mod(fn, options ? : "");
     if (FLAG(v))
       printf("loaded %s '%s': %s\n", fn, options, strerror(errno));
     if (errno == EEXIST) rc = 0;
@@ -489,15 +489,15 @@
   }
 
   // Read /proc/modules to get loaded modules.
-  fs = xfopen("/proc/modules", "r");
-  
-  while (read_line(fs, &procline) > 0) {
+  fs = fopen("/proc/modules", "r");
+
+  while (fs && read_line(fs, &procline) > 0) {
     *strchr(procline, ' ') = 0;
     get_mod(procline, 1)->flags = MOD_ALOADED;
     free(procline);
     procline = NULL;
   }
-  fclose(fs);
+  if (fs) fclose(fs);
   if (FLAG(a) || FLAG(r)) for (; *argv; argv++) add_mod(*argv);
   else {
     add_mod(*argv);
diff -Nru toybox-0.8.8+dfsg/toys/pending/sh.c toybox-0.8.9+dfsg/toys/pending/sh.c
--- toybox-0.8.8+dfsg/toys/pending/sh.c	2022-08-12 07:58:03.000000000 +0000
+++ toybox-0.8.9+dfsg/toys/pending/sh.c	2023-01-10 19:24:45.000000000 +0000
@@ -555,21 +555,18 @@
 // Recursively calculate string into dd, returns 0 if failed, ss = error point
 // Recursion resolves operators of lower priority level to a value
 // Loops through operators at same priority
+#define NO_ASSIGN 128
 static int recalculate(long long *dd, char **ss, int lvl)
 {
   long long ee, ff;
   char *var = 0, *val, cc = **nospace(ss);
-  int ii, assign = 1;
-
-  if (lvl>99) {
-    lvl -= 100;
-    assign = 0;
-  }
+  int ii, noa = lvl&NO_ASSIGN;
+  lvl &= NO_ASSIGN-1;
 
   // Unary prefixes can only occur at the start of a parse context
   if (cc=='!' || cc=='~') {
     ++*ss;
-    if (!recalculate(dd, ss, 15)) return 0;
+    if (!recalculate(dd, ss, noa|15)) return 0;
     *dd = (cc=='!') ? !*dd : ~*dd;
   } else if (cc=='+' || cc=='-') {
     // Is this actually preincrement/decrement? (Requires assignable var.)
@@ -582,12 +579,12 @@
       }
     }
     if (!var) {
-      if (!recalculate(dd, ss, 15)) return 0;
+      if (!recalculate(dd, ss, noa|15)) return 0;
       if (cc=='-') *dd = -*dd;
     }
   } else if (cc=='(') {
     ++*ss;
-    if (!recalculate(dd, ss, 1)) return 0;
+    if (!recalculate(dd, ss, noa|1)) return 0;
     if (**ss!=')') return 0;
     else ++*ss;
   } else if (isdigit(cc)) {
@@ -601,6 +598,7 @@
     // At lvl 0 "" is ok, anything higher needs a non-empty equation
     if (lvl || (cc && cc!=')')) return 0;
     *dd = 0;
+
     return 1;
   }
 
@@ -614,7 +612,7 @@
       return 0;
     }
     val = getvar(var = *ss) ? : "";
-    ii = recalculate(dd, &val, 0);
+    ii = recalculate(dd, &val, noa);
     TT.recursion--;
     if (!ii) return 0;
     if (*val) {
@@ -638,17 +636,15 @@
       *ss += 2;
 
     // Assignment operators: = *= /= %= += -= <<= >>= &= ^= |=
-    } else if (lvl<=2 && (*ss)[ii = !!strchr("*/%+-", **ss)
-               +2*!memcmp(*ss, "<<", 2)+2*!memcmp(*ss, ">>", 2)]=='=')
+    } else if (lvl<=2 && (*ss)[ii = (-1 != stridx("*/%+-", **ss))
+               +2*!smemcmp(*ss, "<<", 2)+2*!smemcmp(*ss, ">>", 2)]=='=')
     {
       // TODO: assignments are lower priority BUT must go after variable,
       // come up with precedence checking tests?
       cc = **ss;
       *ss += ii+1;
-      if (!recalculate(&ee, ss, 1)) return 0; // lvl instead of 1?
+      if (!recalculate(&ee, ss, noa|1)) return 0; // TODO lvl instead of 1?
       if (cc=='*') *dd *= ee;
-      else if (cc=='/') *dd /= ee;
-      else if (cc=='%') *dd %= ee;
       else if (cc=='+') *dd += ee;
       else if (cc=='-') *dd -= ee;
       else if (cc=='<') *dd <<= ee;
@@ -656,87 +652,94 @@
       else if (cc=='&') *dd &= ee;
       else if (cc=='^') *dd ^= ee;
       else if (cc=='|') *dd |= ee;
-      else *dd = ee;
+      else if (!cc) *dd = ee;
+      else if (!ee) {
+        perror_msg("%c0", cc);
+
+        return 0;
+      } else if (cc=='/') *dd /= ee;
+      else if (cc=='%') *dd %= ee;
       ee = *dd;
     }
-    if (cc && assign) setvar(xmprintf("%.*s=%lld", (int)(val-var), var, ee));
+    if (cc && !noa) setvar(xmprintf("%.*s=%lld", (int)(val-var), var, ee));
   }
 
   // x**y binds first
-  if (lvl<=13) while (strstart(nospace(ss), "**")) {
-    if (!recalculate(&ee, ss, 14)) return 0;
+  if (lvl<=14) while (strstart(nospace(ss), "**")) {
+    if (!recalculate(&ee, ss, noa|15)) return 0;
     if (ee<0) perror_msg("** < 0");
     for (ff = *dd, *dd = 1; ee; ee--) *dd *= ff;
   }
 
   // w*x/y%z bind next
-  if (lvl<=12) while ((cc = **nospace(ss)) && strchr("*/%", cc)) {
+  if (lvl<=13) while ((cc = **nospace(ss)) && strchr("*/%", cc)) {
     ++*ss;
-    if (!recalculate(&ee, ss, 13)) return 0;
+    if (!recalculate(&ee, ss, noa|14)) return 0;
     if (cc=='*') *dd *= ee;
-    else if (cc=='%') *dd %= ee;
     else if (!ee) {
-      perror_msg("/0");
+      perror_msg("%c0", cc);
+
       return 0;
-    } else *dd /= ee;
+    } else if (cc=='%') *dd %= ee;
+    else *dd /= ee;
   }
 
   // x+y-z
-  if (lvl<=11) while ((cc = **nospace(ss)) && strchr("+-", cc)) {
+  if (lvl<=12) while ((cc = **nospace(ss)) && strchr("+-", cc)) {
     ++*ss;
-    if (!recalculate(&ee, ss, 12)) return 0;
+    if (!recalculate(&ee, ss, noa|13)) return 0;
     if (cc=='+') *dd += ee;
     else *dd -= ee;
   }
 
   // x<>
 
-  if (lvl<=10) while ((cc = **nospace(ss)) && strchr("<>", cc) && cc==(*ss)[1]){
+  if (lvl<=11) while ((cc = **nospace(ss)) && strchr("<>", cc) && cc==(*ss)[1]){
     *ss += 2;
-    if (!recalculate(&ee, ss, 11)) return 0;
+    if (!recalculate(&ee, ss, noa|12)) return 0;
     if (cc == '<') *dd <<= ee;
     else *dd >>= ee;
   }
 
   // x >=
-  if (lvl<=9) while ((cc = **nospace(ss)) && strchr("<>", cc)) {
+  if (lvl<=10) while ((cc = **nospace(ss)) && strchr("<>", cc)) {
     if ((ii = *++*ss=='=')) ++*ss;
-    if (!recalculate(&ee, ss, 10)) return 0;
+    if (!recalculate(&ee, ss, noa|11)) return 0;
     if (cc=='<') *dd = ii ? (*dd<=ee) : (*dd=ee) : (*dd>ee);
   }
 
-  if (lvl<=8) while ((cc = **nospace(ss)) && strchr("=!", cc) && (*ss)[1]=='='){
+  if (lvl<=9) while ((cc = **nospace(ss)) && strchr("=!", cc) && (*ss)[1]=='='){
     *ss += 2;
-    if (!recalculate(&ee, ss, 9)) return 0;
+    if (!recalculate(&ee, ss, noa|10)) return 0;
     *dd = (cc=='!') ? *dd != ee : *dd == ee;
   }
 
-  if (lvl<=7) while (**nospace(ss)=='&') {
+  if (lvl<=8) while (**nospace(ss)=='&' && (*ss)[1]!='&') {
     ++*ss;
-    if (!recalculate(&ee, ss, 8)) return 0;
+    if (!recalculate(&ee, ss, noa|9)) return 0;
     *dd &= ee;
   }
 
-  if (lvl<=6) while (**nospace(ss)=='^') {
+  if (lvl<=7) while (**nospace(ss)=='^') {
     ++*ss;
-    if (!recalculate(&ee, ss, 7)) return 0;
+    if (!recalculate(&ee, ss, noa|8)) return 0;
     *dd ^= ee;
   }
 
-  if (lvl<=5) while (**nospace(ss)=='|') {
+  if (lvl<=6) while (**nospace(ss)=='|' && (*ss)[1]!='|') {
     ++*ss;
-    if (!recalculate(&ee, ss, 6)) return 0;
+    if (!recalculate(&ee, ss, noa|7)) return 0;
     *dd |= ee;
   }
 
   if (lvl<=5) while (strstart(nospace(ss), "&&")) {
-    if (!recalculate(&ee, ss, 6+100*!!*dd)) return 0;
+    if (!recalculate(&ee, ss, noa|6|NO_ASSIGN*!*dd)) return 0;
     *dd = *dd && ee;
   }
 
   if (lvl<=4) while (strstart(nospace(ss), "||")) {
-    if (!recalculate(&ee, ss, 5+100*!*dd)) return 0;
+    if (!recalculate(&ee, ss, noa|5|NO_ASSIGN*!!*dd)) return 0;
     *dd = *dd || ee;
   }
 
@@ -745,19 +748,19 @@
   if (lvl<=3) if (**nospace(ss)=='?') {
     ++*ss;
     if (**nospace(ss)==':' && *dd) ee = *dd;
-    else if (!recalculate(&ee, ss, 1+100*!*dd) || **nospace(ss)!=':')
+    else if (!recalculate(&ee, ss, noa|1|NO_ASSIGN*!*dd) || **nospace(ss)!=':')
       return 0;
     ++*ss;
-    if (!recalculate(&ff, ss, 1+100*!!*dd)) return 0;
+    if (!recalculate(&ff, ss, noa|1|NO_ASSIGN*!!*dd)) return 0;
     *dd = *dd ? ee : ff;
   }
 
   // lvl<=2 assignment would go here, but handled above because variable
 
   // , (slightly weird, replaces dd instead of modifying it via ee/ff)
-  if (lvl<=1) while ((cc = **nospace(ss)) && cc==',') {
+  if (lvl<=1) while (**nospace(ss)==',') {
     ++*ss;
-    if (!recalculate(dd, ss, 2)) return 0;
+    if (!recalculate(dd, ss, noa|2)) return 0;
   }
 
   return 1;
@@ -881,9 +884,9 @@
   if (flags&VAR_INT) {
     sd = ss;
     if (!recalculate(&ll, &sd, 0) || *sd) {
-     perror_msg("bad math: %s @ %d", ss, (int)(sd-ss));
+      perror_msg("bad math: %s @ %d", ss, (int)(sd-ss));
 
-     goto bad;
+      goto bad;
     }
 
     sprintf(buf, "%lld", ll);
@@ -1572,7 +1575,7 @@
       j = 0;
     }
 
-    // Got wildcard? Return if start of name if out of count, else skip [] ()
+    // Got wildcard? Return start of name if out of count, else skip [] ()
     if (*idxc && p-pattern == (long)deck->v[*idx]) {
       if (!j++ && !count--) return old;
       ++*idx;
@@ -1777,7 +1780,8 @@
 #define SEMI_IFS (1<<6)    // Use ' ' instead of IFS to combine $*
 // expand str appending to arg using above flag defines, add mallocs to delete
 // if ant not null, save wildcard deck there instead of expanding vs filesystem
-// returns 0 for success, 1 for error
+// returns 0 for success, 1 for error.
+// If measure stop at *measure and return input bytes consumed in *measure
 static int expand_arg_nobrace(struct sh_arg *arg, char *str, unsigned flags,
   struct arg_list **delete, struct sh_arg *ant, long *measure)
 {
@@ -1834,10 +1838,7 @@
     ifs = slice = 0;
 
     // handle escapes and quoting
-    if (cc == '\\') {
-      if (!(qq&1) || (str[ii] && strchr("\"\\$`", str[ii])))
-        new[oo++] = str[ii] ? str[ii++] : cc;
-    } else if (cc == '"') qq++;
+    if (cc == '"') qq++;
     else if (cc == '\'') {
       if (qq&1) new[oo++] = cc;
       else {
@@ -1847,12 +1848,12 @@
 
     // both types of subshell work the same, so do $( here not in '$' below
 // TODO $((echo hello) | cat) ala $(( becomes $( ( retroactively
-    } else if (cc == '`' || (cc == '$' && str[ii] && strchr("([", str[ii]))) {
+    } else if (cc == '`' || (cc == '$' && (str[ii]=='(' || str[ii]=='['))) {
       off_t pp = 0;
 
       s = str+ii-1;
       kk = parse_word(s, 1, 0)-s;
-      if (str[ii] == '[' || *toybuf == 255) {
+      if (str[ii] == '[' || *toybuf == 255) { // (( parsed together, not (( ) )
         struct sh_arg aa = {0};
         long long ll;
 
@@ -1900,10 +1901,13 @@
           for (kk = strlen(ifs); kk && ifs[kk-1]=='\n'; ifs[--kk] = 0);
         close(jj);
       }
+    } else if (cc=='\\' || !str[ii]) {
+      if (!(qq&1) || (str[ii] && strchr("\"\\$`", str[ii])))
+        new[oo++] = str[ii] ? str[ii++] : cc;
 
     // $VARIABLE expansions
 
-    } else if (cc == '$') {
+    } else if (cc == '$' && str[ii]) {
       cc = *(ss = str+ii++);
       if (cc=='\'') {
         for (s = str+ii; *s != '\''; oo += wcrtomb(new+oo, unescape2(&s, 0),0));
@@ -2122,7 +2126,7 @@
             }
 
             if (yy != -1) {
-              if (*delete && (*delete)->arg==ifs) ifs[yy] = 0;
+              if (delete && *delete && (*delete)->arg==ifs) ifs[yy] = 0;
               else push_arg(delete, ifs = xstrndup(ifs, yy));
             }
           }
@@ -2149,7 +2153,7 @@
                 ll++;
                 continue;
               }
-              if (*delete && (*delete)->arg==ifs) {
+              if (delete && *delete && (*delete)->arg==ifs) {
                 if (jj==dd) memcpy(ifs+ll, ss, jj);
                 else if (jjcommas[0] = i;
+    // end of string: abort unfinished spans and end loop
+    } else if (!old[i]) {
+      for (bb = blist; bb;) {
+        if (!bb->active) {
+          if (bb==blist) {
+            dlist_pop(&blist);
+            bb = blist;
+          } else dlist_pop(&bb);
+        } else bb = (bb->next==blist) ? 0 : bb->next;
+      }
+      break;
+    // no active span?
+    } else if (!bb) continue;
+    // end current span
+    else if (old[i] == '}') {
       bb->active = bb->commas[bb->cnt+1] = i;
-      // pop brace from bb into bnext
-      for (bnext = bb; bb && bb->active; bb = (bb==blist) ? 0 : bb->prev);
       // Is this a .. span?
-      j = 1+*bnext->commas;
-      if (old[i] && !bnext->cnt && i-j>=4) {
+      j = 1+*bb->commas;
+      if (!bb->cnt && i-j>=4) {
         // a..z span? Single digit numbers handled here too. TODO: utf8
         if (old[j+1]=='.' && old[j+2]=='.') {
-          bnext->commas[2] = old[j];
-          bnext->commas[3] = old[j+3];
+          bb->commas[2] = old[j];
+          bb->commas[3] = old[j+3];
           k = 0;
           if (old[j+4]=='}' ||
-            (sscanf(old+j+4, "..%u}%n", bnext->commas+4, &k) && k))
-              bnext->cnt = -1;
+            (sscanf(old+j+4, "..%u}%n", bb->commas+4, &k) && k))
+              bb->cnt = -1;
         }
         // 3..11 numeric span?
-        if (!bnext->cnt) {
-          for (k=0, j = 1+*bnext->commas; k<3; k++, j += x)
-            if (!sscanf(old+j, "..%u%n"+2*!k, bnext->commas+2+k, &x)) break;
-          if (old[j] == '}') bnext->cnt = -2;
+        if (!bb->cnt) {
+          for (k=0, j = 1+*bb->commas; k<3; k++, j += x)
+            if (!sscanf(old+j, "..%u%n"+2*!k, bb->commas+2+k, &x)) break;
+          if (old[j]=='}') bb->cnt = -2;
         }
         // Increment goes in the right direction by at least 1
-        if (bnext->cnt) {
-          if (!bnext->commas[4]) bnext->commas[4] = 1;
-          if ((bnext->commas[3]-bnext->commas[2]>0) != (bnext->commas[4]>0))
-            bnext->commas[4] *= -1;
+        if (bb->cnt) {
+          if (!bb->commas[4]) bb->commas[4] = 1;
+          if ((bb->commas[3]-bb->commas[2]>0) != (bb->commas[4]>0))
+            bb->commas[4] *= -1;
         }
       }
-      // discard unterminated span, or commaless span that wasn't x..y
-      if (!old[i] || !bnext->cnt)
-        free(dlist_pop((blist == bnext) ? &blist : &bnext));
-    // starting brace
-    } else if (old[i] == '{') {
-      dlist_add_nomalloc((void *)&blist,
-        (void *)(bb = xzalloc(sizeof(struct sh_brace)+34*4)));
-      bb->commas[0] = i;
-    // no active span?
-    } else if (!bb) continue;
+      // discard commaless span that wasn't x..y
+      if (!bb->cnt) free(dlist_pop((blist==bb) ? &blist : &bb));
+      // Set bb to last unfinished brace (if any)
+      for (bb = blist ? blist->prev : 0; bb && bb->active;
+           bb = (bb==blist) ? 0 : bb->prev);
     // add a comma to current span
-    else if (bb && old[i] == ',') {
+    } else if (old[i] == ',') {
       if (bb->cnt && !(bb->cnt&31)) {
         dlist_lpop(&blist);
         dlist_add_nomalloc((void *)&blist,
@@ -2740,7 +2754,7 @@
 
   // Skip [[ ]] and (( )) contents for now
   if ((s = arg->v[envlen])) {
-    if (!memcmp(s, "((", 2)) skiplen = 1;
+    if (!smemcmp(s, "((", 2)) skiplen = 1;
     else if (!strcmp(s, "[[")) while (strcmp(arg->v[envlen+skiplen++], "]]"));
   }
   pp = expand_redir(arg, envlen+skiplen, 0);
@@ -2804,7 +2818,7 @@
 //   Several NOFORK can just NOP in a pipeline? Except ${a?b} still errors
 
   // ((math))
-  else if (!memcmp(s = *pp->arg.v, "((", 2)) {
+  else if (!smemcmp(s = *pp->arg.v, "((", 2)) {
     char *ss = s+2;
     long long ll;
 
@@ -2855,6 +2869,7 @@
       }
       toys.rebound = 0;
       pp->exit = toys.exitval;
+      clearerr(stdout);
       if (toys.optargs != toys.argv+1) free(toys.optargs);
       if (toys.old_umask) umask(toys.old_umask);
       memcpy(&toys, &temp, jj);
@@ -3073,7 +3088,7 @@
       }
 
       // "for" on its own line is an error.
-      if (arg->c == 1 && ex && !memcmp(ex, "do\0A", 4)) {
+      if (arg->c == 1 && ex && !smemcmp(ex, "do\0A", 4)) {
         s = "newline";
         goto flush;
       }
@@ -3167,7 +3182,7 @@
         free(s);
         s = 0;
 // TODO can't have ; between "for i" and in or do. (Newline yes, ; no. Why?)
-        if (!arg->c && ex && !memcmp(ex, "do\0C", 4)) continue;
+        if (!arg->c && ex && !smemcmp(ex, "do\0C", 4)) continue;
 
       // ;; and friends only allowed in case statements
       } else if (*s == ';') goto flush;
@@ -3179,7 +3194,7 @@
       continue;
 
     // a for/select must have at least one additional argument on same line
-    } else if (ex && !memcmp(ex, "do\0A", 4)) {
+    } else if (ex && !smemcmp(ex, "do\0A", 4)) {
 
       // Sanity check and break the segment
       if (strncmp(s, "((", 2) && *varend(s)) goto flush;
@@ -3198,7 +3213,7 @@
 
     // The "test" part of for/select loops can have (at most) one "in" line,
     // for {((;;))|name [in...]} do
-    if (ex && !memcmp(ex, "do\0C", 4)) {
+    if (ex && !smemcmp(ex, "do\0C", 4)) {
       if (strcmp(s, "do")) {
         // can only have one "in" line between for/do, but not with for(())
         if (pl->prev->type == 's') goto flush;
@@ -3230,7 +3245,7 @@
 
     // Expecting NULL means any statement (don't care which).
     if (!ex && *expect) {
-      if (pl->prev->type == 'f' && !end && memcmp(s, "((", 2)) goto flush;
+      if (pl->prev->type == 'f' && !end && smemcmp(s, "((", 2)) goto flush;
       free(dlist_lpop(expect));
     }
 
diff -Nru toybox-0.8.8+dfsg/toys/pending/strace.c toybox-0.8.9+dfsg/toys/pending/strace.c
--- toybox-0.8.8+dfsg/toys/pending/strace.c	2022-08-12 07:58:03.000000000 +0000
+++ toybox-0.8.9+dfsg/toys/pending/strace.c	2023-01-10 19:24:45.000000000 +0000
@@ -245,7 +245,7 @@
           "st_nlink=%ld, st_uid=%d, st_gid=%d, st_blksize=%ld, st_blocks=%ld, "
           "st_size=%lld, st_atime=%ld, st_mtime=%ld, st_ctime=%ld}",
           dev_major(sb.st_dev), dev_minor(sb.st_dev), sb.st_ino, sb.st_mode,
-          sb.st_nlink, sb.st_uid, sb.st_gid, sb.st_blksize, sb.st_blocks,
+          (long) sb.st_nlink, sb.st_uid, sb.st_gid, sb.st_blksize, sb.st_blocks,
           (long long)sb.st_size, sb.st_atime, sb.st_mtime, sb.st_ctime);
     } else {
       fprintf(stderr, "{st_mode=%o, st_size=%lld, ...}", sb.st_mode,
@@ -411,7 +411,7 @@
                 if (!(s = num_to_sig(v))) fprintf(stderr, "%ld", v);
                 else fprintf(stderr, "SIG%s", s);
                 break;
-      case 'z': fprintf(stderr, "%zd", v); break; // size_t
+      case 'z': fprintf(stderr, "%ld", (long) v); break; // size_t
       case 'x': fprintf(stderr, "%lx", v); break; // hex
 
       case '{': print_struct(v); break;
@@ -497,7 +497,9 @@
     SC(mremap, "pzzdp"); // TODO: flags
     SC(munmap, "pz");
     SC(nanosleep, "{timespec}/{timespec}");
+#ifdef __NR_newfstatat
     SC(newfstatat, "fs/{stat}d");
+#endif
     SC(open, "sd|open|m");
     SC(openat, "fs|open|m");
     SC(poll, "pdd");
diff -Nru toybox-0.8.8+dfsg/toys/posix/cksum.c toybox-0.8.9+dfsg/toys/posix/cksum.c
--- toybox-0.8.8+dfsg/toys/posix/cksum.c	2022-08-12 07:58:03.000000000 +0000
+++ toybox-0.8.9+dfsg/toys/posix/cksum.c	2023-01-10 19:24:45.000000000 +0000
@@ -39,24 +39,23 @@
   unsigned crc_table[256];
 )
 
-static unsigned cksum_be(unsigned crc, unsigned char c)
+static unsigned cksum_be(unsigned crc, char c)
 {
-  return (crc<<8)^TT.crc_table[(crc>>24)^c];
+  return (crc<<8) ^ TT.crc_table[(crc>>24)^c];
 }
 
-static unsigned cksum_le(unsigned crc, unsigned char c)
+static unsigned cksum_le(unsigned crc, char c)
 {
   return TT.crc_table[(crc^c)&0xff] ^ (crc>>8);
 }
 
 static void do_cksum(int fd, char *name)
 {
-  unsigned crc = (toys.optflags & FLAG_P) ? 0xffffffff : 0;
-  uint64_t llen = 0, llen2;
-  unsigned (*cksum)(unsigned crc, unsigned char c);
+  unsigned (*cksum)(unsigned crc, char c), crc = FLAG(P) ? ~0 : 0;
+  unsigned long long llen = 0, llen2;
   int len, i;
 
-  cksum = (toys.optflags & FLAG_L) ? cksum_le : cksum_be;
+  cksum = FLAG(L) ? cksum_le : cksum_be;
   // CRC the data
 
   for (;;) {
@@ -70,24 +69,17 @@
 
   // CRC the length
 
-  llen2 = llen;
-  if (!(toys.optflags & FLAG_N)) {
-    while (llen) {
-      crc = cksum(crc, llen);
-      llen >>= 8;
-    }
-  }
+  if (!FLAG(N)) for (llen2 = llen; llen2; llen2 >>= 8) crc = cksum(crc, llen2);
 
-  printf((toys.optflags & FLAG_H) ? "%08x" : "%u",
-    (toys.optflags & FLAG_I) ? crc : ~crc);
-  if (!(toys.optflags&FLAG_N)) printf(" %"PRIu64, llen2);
+  printf(FLAG(H) ? "%08x" : "%u", FLAG(I) ? crc : ~crc);
+  if (!FLAG(N)) printf(" %llu", llen);
   if (toys.optc) printf(" %s", name);
   xputc('\n');
 }
 
 void cksum_main(void)
 {
-  crc_init(TT.crc_table, toys.optflags & FLAG_L);
+  crc_init(TT.crc_table, FLAG(L));
   loopfiles(toys.optargs, do_cksum);
 }
 
diff -Nru toybox-0.8.8+dfsg/toys/posix/comm.c toybox-0.8.9+dfsg/toys/posix/comm.c
--- toybox-0.8.8+dfsg/toys/posix/comm.c	2022-08-12 07:58:03.000000000 +0000
+++ toybox-0.8.9+dfsg/toys/posix/comm.c	2023-01-10 19:24:45.000000000 +0000
@@ -43,26 +43,26 @@
 {
   FILE *file[2];
   char *line[2];
-  int i;
+  int i = 0;
 
-  if (toys.optflags == 7) return;
-
-  for (i = 0; i < 2; i++) {
-    file[i] = xfopen(toys.optargs[i], "r");
+  for (i = 0; i<2; i++) {
+    file[i] = strcmp(toys.optargs[i], "-")?xfopen(toys.optargs[i], "r"):stdin;
     line[i] = xgetline(file[i]);
   }
 
+  if (toys.optflags == 7) return;
+
   while (line[0] && line[1]) {
     int order = strcmp(line[0], line[1]);
 
-    if (order == 0) {
+    if (!order) {
       writeline(line[0], 2);
       for (i = 0; i < 2; i++) {
         free(line[i]);
         line[i] = xgetline(file[i]);
       }
     } else {
-      i = order < 0 ? 0 : 1;
+      i = order>0;
       writeline(line[i], i);
       free(line[i]);
       line[i] = xgetline(file[i]);
@@ -76,5 +76,5 @@
     line[i] = xgetline(file[i]);
   }
 
-  if (CFG_TOYBOX_FREE) for (i = 0; i < 2; i++) fclose(file[i]);
+  if (CFG_TOYBOX_FREE) fclose(file[0]), fclose(file[1]);
 }
diff -Nru toybox-0.8.8+dfsg/toys/posix/cp.c toybox-0.8.9+dfsg/toys/posix/cp.c
--- toybox-0.8.8+dfsg/toys/posix/cp.c	2022-08-12 07:58:03.000000000 +0000
+++ toybox-0.8.9+dfsg/toys/posix/cp.c	2023-01-10 19:24:45.000000000 +0000
@@ -16,8 +16,8 @@
 // for FLAG macros to work out right in shared infrastructure.
 
 USE_CP(NEWTOY(cp, "<1(preserve):;D(parents)RHLPprudaslvnF(remove-destination)fit:T[-HLPd][-niu][+Rr]", TOYFLAG_BIN))
-USE_MV(NEWTOY(mv, "<1vnF(remove-destination)fit:T[-ni]", TOYFLAG_BIN))
-USE_INSTALL(NEWTOY(install, "<1cdDpsvt:m:o:g:", TOYFLAG_USR|TOYFLAG_BIN))
+USE_MV(NEWTOY(mv, "<1v(verbose)nF(remove-destination)fit:T[-ni]", TOYFLAG_BIN))
+USE_INSTALL(NEWTOY(install, "<1cdDp(preserve-timestamps)svt:m:o:g:", TOYFLAG_USR|TOYFLAG_BIN))
 
 config CP
   bool "cp"
diff -Nru toybox-0.8.8+dfsg/toys/posix/cpio.c toybox-0.8.9+dfsg/toys/posix/cpio.c
--- toybox-0.8.8+dfsg/toys/posix/cpio.c	2022-08-12 07:58:03.000000000 +0000
+++ toybox-0.8.9+dfsg/toys/posix/cpio.c	2023-01-10 19:24:45.000000000 +0000
@@ -132,7 +132,7 @@
       if (empty) error_exit("empty archive");
       else break;
     }
-    if (size != 110 || memcmp(toybuf, "070701", 6)) error_exit("bad header");
+    if (size != 110 || smemcmp(toybuf, "070701", 6)) error_exit("bad header");
     tofree = name = strpad(afd, x8u(toybuf+94), 110);
     if (!strcmp("TRAILER!!!", name)) {
       free(tofree);
diff -Nru toybox-0.8.8+dfsg/toys/posix/date.c toybox-0.8.9+dfsg/toys/posix/date.c
--- toybox-0.8.8+dfsg/toys/posix/date.c	2022-08-12 07:58:03.000000000 +0000
+++ toybox-0.8.9+dfsg/toys/posix/date.c	2023-01-10 19:24:45.000000000 +0000
@@ -7,7 +7,7 @@
  * Note: setting a 2 year date is 50 years back/forward from today,
  * not posix's hardwired magic dates.
 
-USE_DATE(NEWTOY(date, "d:D:I(iso)(iso-8601):;r:s:u(utc)[!dr]", TOYFLAG_BIN))
+USE_DATE(NEWTOY(date, "d:D:I(iso-8601):;r:s:u(utc)[!dr]", TOYFLAG_BIN))
 
 config DATE
   bool "date"
@@ -156,7 +156,7 @@
       struct tm tm = {};
       char *s = strptime(TT.d, TT.D+(*TT.D=='+'), &tm);
 
-      t = (s && *s) ? xvali_date(&tm, s) : xvali_date(0, TT.d);
+      t = (s && !*s) ? xvali_date(&tm, s) : xvali_date(0, TT.d);
     } else parse_date(TT.d, &t);
   } else {
     struct timespec ts;
diff -Nru toybox-0.8.8+dfsg/toys/posix/df.c toybox-0.8.9+dfsg/toys/posix/df.c
--- toybox-0.8.8+dfsg/toys/posix/df.c	2022-08-12 07:58:03.000000000 +0000
+++ toybox-0.8.9+dfsg/toys/posix/df.c	2023-01-10 19:24:45.000000000 +0000
@@ -4,7 +4,7 @@
  *
  * See http://opengroup.org/onlinepubs/9699919799/utilities/df.html
 
-USE_DF(NEWTOY(df, "HPkhit*a[-HPh]", TOYFLAG_SBIN))
+USE_DF(NEWTOY(df, "HPkhit*a[-HPh]", TOYFLAG_BIN))
 
 config DF
   bool "df"
diff -Nru toybox-0.8.8+dfsg/toys/posix/file.c toybox-0.8.9+dfsg/toys/posix/file.c
--- toybox-0.8.8+dfsg/toys/posix/file.c	2022-08-12 07:58:03.000000000 +0000
+++ toybox-0.8.9+dfsg/toys/posix/file.c	2023-01-10 19:24:45.000000000 +0000
@@ -4,7 +4,7 @@
  *
  * See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/file.html
 
-USE_FILE(NEWTOY(file, "<1bhLs[!hL]", TOYFLAG_USR|TOYFLAG_BIN))
+USE_FILE(NEWTOY(file, "<1b(brief)hLs[!hL]", TOYFLAG_USR|TOYFLAG_BIN))
 
 config FILE
   bool "file"
@@ -177,10 +177,10 @@
           goto bad;
         }
 
-        if (n_namesz==4 && !memcmp(note+12, "GNU", 4) && n_type==3) {
+        if (n_namesz==4 && !smemcmp(note+12, "GNU", 4) && n_type==3) {
           printf(", BuildID=");
           for (j = 0; j= 4) {
             printf(", for Android %d", (int)elf_int(note+20, 4));
             // NDK r14 and later also include NDK version info. OS binaries
@@ -244,7 +244,7 @@
       s-3, (int)peek_le(s, 2), (int)peek_le(s+2, 2));
 
   // TODO: parsing JPEG for width/height is harder than GIF or PNG.
-  else if (len>32 && !memcmp(s, "\xff\xd8", 2)) xputs("JPEG image data");
+  else if (len>32 && !smemcmp(s, "\xff\xd8", 2)) xputs("JPEG image data");
 
   else if (len>8 && strstart(&s, "\xca\xfe\xba\xbe")) {
     unsigned count = peek_be(s, 4), i, arch;
@@ -307,7 +307,7 @@
   else if (len>31 && peek_be(s, 7) == 0xfd377a585a0000UL)
     xputs("xz compressed data");
   else if (len>10 && strstart(&s, "\x1f\x8b")) xputs("gzip compressed data");
-  else if (len>32 && !memcmp(s+1, "\xfa\xed\xfe", 3)) {
+  else if (len>32 && !smemcmp(s+1, "\xfa\xed\xfe", 3)) {
     int bit = (*s==0xce) ? 32 : 64;
     char *what = 0;
 
@@ -325,26 +325,26 @@
     else what = NULL;
     if (what) xprintf("%s\n", what);
     else xprintf("(bad type %d)\n", s[9]);
-  } else if (len>36 && !memcmp(s, "OggS\x00\x02", 6)) {
+  } else if (len>36 && !smemcmp(s, "OggS\x00\x02", 6)) {
     xprintf("Ogg data");
     // https://wiki.xiph.org/MIMETypesCodecs
-    if (!memcmp(s+28, "CELT    ", 8)) xprintf(", celt audio");
-    else if (!memcmp(s+28, "CMML    ", 8)) xprintf(", cmml text");
-    else if (!memcmp(s+28, "BBCD\0", 5)) xprintf(", dirac video");
-    else if (!memcmp(s+28, "\177FLAC", 5)) xprintf(", flac audio");
-    else if (!memcmp(s+28, "\x8bJNG\r\n\x1a\n", 8)) xprintf(", jng video");
-    else if (!memcmp(s+28, "\x80kate\0\0\0", 8)) xprintf(", kate text");
-    else if (!memcmp(s+28, "OggMIDI\0", 8)) xprintf(", midi text");
-    else if (!memcmp(s+28, "\x8aMNG\r\n\x1a\n", 8)) xprintf(", mng video");
-    else if (!memcmp(s+28, "OpusHead", 8)) xprintf(", opus audio");
-    else if (!memcmp(s+28, "PCM     ", 8)) xprintf(", pcm audio");
-    else if (!memcmp(s+28, "\x89PNG\r\n\x1a\n", 8)) xprintf(", png video");
-    else if (!memcmp(s+28, "Speex   ", 8)) xprintf(", speex audio");
-    else if (!memcmp(s+28, "\x80theora", 7)) xprintf(", theora video");
-    else if (!memcmp(s+28, "\x01vorbis", 7)) xprintf(", vorbis audio");
-    else if (!memcmp(s+28, "YUV4MPEG", 8)) xprintf(", yuv4mpeg video");
+    if (!smemcmp(s+28, "CELT    ", 8)) xprintf(", celt audio");
+    else if (!smemcmp(s+28, "CMML    ", 8)) xprintf(", cmml text");
+    else if (!smemcmp(s+28, "BBCD", 5)) xprintf(", dirac video");
+    else if (!smemcmp(s+28, "\177FLAC", 5)) xprintf(", flac audio");
+    else if (!smemcmp(s+28, "\x8bJNG\r\n\x1a\n", 8)) xprintf(", jng video");
+    else if (!smemcmp(s+28, "\x80kate\0\0", 8)) xprintf(", kate text");
+    else if (!smemcmp(s+28, "OggMIDI", 8)) xprintf(", midi text");
+    else if (!smemcmp(s+28, "\x8aMNG\r\n\x1a\n", 8)) xprintf(", mng video");
+    else if (!smemcmp(s+28, "OpusHead", 8)) xprintf(", opus audio");
+    else if (!smemcmp(s+28, "PCM     ", 8)) xprintf(", pcm audio");
+    else if (!smemcmp(s+28, "\x89PNG\r\n\x1a\n", 8)) xprintf(", png video");
+    else if (!smemcmp(s+28, "Speex   ", 8)) xprintf(", speex audio");
+    else if (!smemcmp(s+28, "\x80theora", 7)) xprintf(", theora video");
+    else if (!smemcmp(s+28, "\x01vorbis", 7)) xprintf(", vorbis audio");
+    else if (!smemcmp(s+28, "YUV4MPEG", 8)) xprintf(", yuv4mpeg video");
     xputc('\n');
-  } else if (len>32 && !memcmp(s, "RIF", 3) && !memcmp(s+8, "WAVEfmt ", 8)) {
+  } else if (len>32 && !smemcmp(s, "RIF", 3) && !smemcmp(s+8, "WAVEfmt ", 8)) {
     // https://en.wikipedia.org/wiki/WAV
     int le = (s[3] == 'F');
     int format = le ? peek_le(s+20, 2) : peek_be(s+20, 2);
@@ -372,7 +372,7 @@
     else xprintf("unknown format %d", format);
     xputc('\n');
   } else if (len>12 && peek_be(s, 4)==0x10000) xputs("TrueType font");
-  else if (len>12 && !memcmp(s, "ttcf\x00", 5)) {
+  else if (len>12 && !smemcmp(s, "ttcf", 5)) {
     xprintf("TrueType font collection, version %d, %d fonts\n",
             (int)peek_be(s+4, 2), (int)peek_be(s+8, 4));
 
@@ -382,16 +382,14 @@
   else if (strstart(&s,"-----BEGIN CERTIFICATE-----")) xputs("PEM certificate");
 
   // https://msdn.microsoft.com/en-us/library/windows/desktop/ms680547(v=vs.85).aspx
-  else if (len>0x70 && !memcmp(s, "MZ", 2) &&
-      (magic=peek_le(s+0x3c,4))0x70 && !smemcmp(s, "MZ", 2) &&
+      (magic=peek_le(s+0x3c,4))0x32 && !memcmp(s, "BM", 2) && !peek_be(s+6, 4)) {
+  } else if (len>0x32 && !smemcmp(s, "BM", 2) && !peek_be(s+6, 4)) {
     xprintf("BMP image, %d x %d, %d bpp\n", (int)peek_le(s+18, 4),
             (int)peek_le(s+22,4), (int)peek_le(s+28, 2));
 
@@ -436,7 +432,7 @@
         (int)peek_le(s+12, 4), (int)peek_le(s+20, 4));
 
     // https://android.googlesource.com/platform/system/tools/mkbootimg/+/refs/heads/master/include/bootimg/bootimg.h
-  } else if (len>1632 && !memcmp(s, "ANDROID!", 8)) {
+  } else if (len>1632 && !smemcmp(s, "ANDROID!", 8)) {
     xprintf("Android boot image v%d\n", (int)peek_le(s+40, 4));
 
     // https://source.android.com/devices/architecture/dto/partitions
@@ -445,7 +441,7 @@
             (int)peek_be(s+16, 4));
 
     // frameworks/base/core/java/com/android/internal/util/BinaryXmlSerializer.java
-  } else if (len>4 && !memcmp(s, "ABX", 3)) {
+  } else if (len>4 && !smemcmp(s, "ABX", 3)) {
     xprintf("Android Binary XML v%d\n", s[3]);
 
     // Text files, including shell scripts.
diff -Nru toybox-0.8.8+dfsg/toys/posix/find.c toybox-0.8.9+dfsg/toys/posix/find.c
--- toybox-0.8.8+dfsg/toys/posix/find.c	2022-08-12 07:58:03.000000000 +0000
+++ toybox-0.8.9+dfsg/toys/posix/find.c	2023-01-10 19:24:45.000000000 +0000
@@ -617,7 +617,7 @@
             ff = 0;
             ch = *fmt;
 
-            // long long is its own stack size on LP64, so handle seperately
+            // long long is its own stack size on LP64, so handle separately
             if (ch == 'i' || ch == 's') {
               strcpy(next+len, "lld");
               printf(next, (ch == 'i') ? (long long)new->st.st_ino
diff -Nru toybox-0.8.8+dfsg/toys/posix/grep.c toybox-0.8.9+dfsg/toys/posix/grep.c
--- toybox-0.8.8+dfsg/toys/posix/grep.c	2022-08-12 07:58:03.000000000 +0000
+++ toybox-0.8.9+dfsg/toys/posix/grep.c	2023-01-10 19:24:45.000000000 +0000
@@ -4,13 +4,10 @@
  *
  * See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/grep.html
  *
- * Posix doesn't even specify -r, documenting deviations from it is silly.
-* echo hello | grep -w ''
-* echo '' | grep -w ''
-* echo hello | grep -f next)
-      shoe->rc = 0;
+    for (shoe = (void *)TT.reg; shoe; shoe = shoe->next) shoe->rc = 0;
 
     // Loop to handle multiple matches in same line
     do {
       regmatch_t *mm = (void *)toybuf;
+      struct arg_list *seek;
 
-      // Handle "fixed" (literal) matches
-      if (FLAG(F)) {
-        struct arg_list *seek, fseek;
-        char *s = 0;
-
-        for (seek = TT.e; seek; seek = seek->next) {
-          if (FLAG(x)) {
-            if (!(FLAG(i) ? strcasecmp : strcmp)(seek->arg, line)) s = line;
-          } else if (!*seek->arg) {
-            // No need to set fseek.next because this will match every line.
-            seek = &fseek;
-            fseek.arg = s = line;
-          } else if (FLAG(i)) s = strcasestr(start, seek->arg);
-          else s = strstr(start, seek->arg);
+      mm->rm_so = mm->rm_eo = 0;
+      rc = 1;
 
-          if (s) break;
+      // Handle "fixed" (literal) matches (if any)
+      if (TT.e && *start) for (ss = start; ss-linenext) {
+          if (*(pp = seek->arg)=='^' && !FLAG(F)) {
+            if (ss!=start) continue;
+            pp++;
+          }
+          for (ii = 0; pp[ii] && ss[ii]; ii++) {
+            if (!FLAG(F)) {
+              if (pp[ii]=='.') continue;
+              if (pp[ii]=='\\' && pp[ii+1]) pp++;
+              else if (pp[ii]=='$' && !pp[ii+1]) break;
+            }
+            if (FLAG(i)) {
+              if (toupper(pp[ii])!=toupper(ss[ii])) break;
+            } else if (pp[ii]!=ss[ii]) break;
+          }
+          if (pp[ii] && (pp[ii]!='$' || pp[ii+1] || ss[ii])) continue;
+          mm->rm_eo = (mm->rm_so = ss-start)+ii;
+          rc = 0;
+
+          goto got;
         }
+        if (FLAG(x)) break;
+      }
 
-        if (s) {
-          rc = 0;
-          mm->rm_so = (s-start);
-          mm->rm_eo = (s-start)+strlen(seek->arg);
-        } else rc = 1;
-
-      // Handle regex matches
-      } else {
-        int baseline = mm->rm_eo;
-
-        mm->rm_so = mm->rm_eo = INT_MAX;
-        rc = 1;
-        for (shoe = (void *)TT.reg; shoe; shoe = shoe->next) {
-
-          // Do we need to re-check this regex?
-          if (!shoe->rc) {
-            shoe->m.rm_so -= baseline;
-            shoe->m.rm_eo -= baseline;
-            if (!matched || shoe->m.rm_so<0)
-              shoe->rc = regexec0(&shoe->r, start, ulen-(start-line), 1,
-                                  &shoe->m, start==line ? 0 : REG_NOTBOL);
-          }
+      // Empty pattern always matches
+      if (rc && *TT.fixed && !FLAG(o)) rc = 0;
+got:
+      // Handle regex matches (if any)
+      for (shoe = (void *)TT.reg; shoe; shoe = shoe->next) {
+        // Do we need to re-check this regex?
+        if (!shoe->rc) {
+          shoe->m.rm_so -= move;
+          shoe->m.rm_eo -= move;
+          if (!matched || shoe->m.rm_so<0)
+            shoe->rc = regexec0(&shoe->r, start, ulen-(start-line), 1,
+                                &shoe->m, start==line ? 0 : REG_NOTBOL);
+        }
 
-          // If we got a match, is it a _better_ match?
-          if (!shoe->rc && (shoe->m.rm_so < mm->rm_so ||
-              (shoe->m.rm_so == mm->rm_so && shoe->m.rm_eo >= mm->rm_eo)))
-          {
-            mm = &shoe->m;
-            rc = 0;
-          }
+        // If we got a match, is it a _better_ match?
+        if (!shoe->rc && (rc || shoe->m.rm_so < mm->rm_so ||
+            (shoe->m.rm_so == mm->rm_so && shoe->m.rm_eo >= mm->rm_eo)))
+        {
+          mm = &shoe->m;
+          rc = 0;
         }
       }
 
       if (!rc && FLAG(o) && !mm->rm_eo && ulen>start-line) {
-        start++;
+        move = 1;
         continue;
       }
 
@@ -238,7 +235,7 @@
           if (!isalnum(c) && c != '_') c = 0;
         }
         if (c) {
-          start += mm->rm_so+1;
+          move = mm->rm_so+1;
           continue;
         }
       }
@@ -247,7 +244,7 @@
         if (FLAG(o)) {
           if (rc) mm->rm_eo = ulen-(start-line);
           else if (!mm->rm_so) {
-            start += mm->rm_eo;
+            move = mm->rm_eo;
             continue;
           } else mm->rm_eo = mm->rm_so;
         } else {
@@ -271,7 +268,7 @@
         xexit();
       }
       if (FLAG(L) || FLAG(l)) {
-        if (FLAG(l)) xprintf("%s%c", name, TT.outdelim);
+        if (FLAG(l)) xprintf("%s%c", name, '\n'*!FLAG(Z));
         free(line);
         fclose(file);
         return;
@@ -308,9 +305,8 @@
         }
       }
 
-      start += mm->rm_eo;
-      if (mm->rm_so == mm->rm_eo) break;
-    } while (*start);
+      if (mm->rm_so == (move = mm->rm_eo)) break;
+    } while (*(start += move));
     offset += len;
 
     if (matched) {
@@ -318,7 +314,7 @@
       if (FLAG(color) && !FLAG(o)) {
         xputsn(TT.grey);
         if (ulen > start-line) xputsl(start, ulen-(start-line));
-        xputc(TT.outdelim);
+        xputc(TT.delim);
       }
       mcount++;
     } else {
@@ -355,7 +351,7 @@
     if (FLAG(m) && mcount >= TT.m) break;
   }
 
-  if (FLAG(L)) xprintf("%s%c", name, TT.outdelim);
+  if (FLAG(L)) xprintf("%s%c", name, TT.delim);
   else if (FLAG(c)) outline(0, ':', name, mcount, 0, 1);
 
   // loopfiles will also close the fd, but this frees an (opaque) struct.
@@ -368,21 +364,32 @@
   }
 }
 
+static int lensort(struct arg_list **a, struct arg_list **b)
+{
+  long la = strlen((*a)->arg), lb = strlen((*b)->arg);
+
+  if (lalb) return 1;
+
+  return 0;
+}
+
 static void parse_regex(void)
 {
-  struct arg_list *al, *new, *list = NULL;
-  char *s, *ss;
+  struct arg_list *al, *new, *list = NULL, **last;
+  char *s, *ss, *special = "\\.^$[()|*+?{";
+  int len, ii, key;
 
   // Add all -f lines to -e list. (Yes, this is leaking allocation context for
   // exit to free. Not supporting nofork for this command any time soon.)
   al = TT.f ? TT.f : TT.e;
   while (al) {
     if (TT.f) {
-      if (!*(s = ss = xreadfile(al->arg, 0, 0))) {
-        free(ss);
+      if (!*(s = xreadfile(al->arg, 0, 0))) {
+        free(s);
         s = 0;
-      }
-    } else s = ss = al->arg;
+      } else if (*(ss = s+strlen(s)-1)=='\n') *ss = 0;
+    } else s = al->arg;
 
     // Advance, when we run out of -f switch to -e.
     al = al->next;
@@ -392,30 +399,70 @@
     }
     if (!s) continue;
 
+    // NOTE: even with -z, -f is still \n delimited. Blank line = match all
     // Split lines at \n, add individual lines to new list.
     do {
-      ss = FLAG(z) ? 0 : strchr(s, '\n');
-      if (ss) *(ss++) = 0;
+      if ((ss = strchr(s, '\n'))) *(ss++) = 0;
       new = xmalloc(sizeof(struct arg_list));
       new->next = list;
       new->arg = s;
       list = new;
       s = ss;
-    } while (ss && *s);
+    } while (s);
   }
   TT.e = list;
 
-  if (!FLAG(F)) {
-    // Convert regex list
-    for (al = TT.e; al; al = al->next) {
+  // Convert to regex where appropriate
+  for (last = &TT.e; *last;) {
+    // Can we use the fast path?
+    s = (*last)->arg;
+    if ('.'!=*s && !FLAG(F) && strcmp(s, "^$")) for (; *s; s++) {
+      if (*s=='\\') {
+        if (!s[1] || !strchr(special, *++s)) break;
+        if (!FLAG(E) && *s=='(') break;
+      } else if (*s>127 || strchr(special+4, *s)) break;
+    }
+
+    // Add entry to fast path (literal-ish match) or slow path (regexec)
+    if (!*s || FLAG(F)) last = &((*last)->next);
+    else {
       struct reg *shoe;
 
-      if (FLAG(o) && !*al->arg) continue;
       dlist_add_nomalloc(&TT.reg, (void *)(shoe = xmalloc(sizeof(struct reg))));
-      xregcomp(&shoe->r, al->arg,
+      xregcomp(&shoe->r, (*last)->arg,
                (REG_EXTENDED*!!FLAG(E))|(REG_ICASE*!!FLAG(i)));
+      al = *last;
+      *last = (*last)->next;
+      free(al);
+    }
+  }
+  dlist_terminate(TT.reg);
+
+  // Sort fast path patterns into buckets by first character
+  for (al = TT.e; al; al = new) {
+    new = al->next;
+    if (FLAG(F)) key = 0;
+    else {
+      key = '^'==*al->arg;
+      if ('\\'==al->arg[key]) key++;
+      else if ('$'==al->arg[key] && !al->arg[key+1]) key++;
     }
-    dlist_terminate(TT.reg);
+    key = al->arg[key];
+    if (FLAG(i)) key = toupper(key);
+    al->next = TT.fixed[key];
+    TT.fixed[key] = al;
+  }
+
+  // Sort each fast path pattern set by length so first hit is longest match
+  if (TT.e) for (key = 0; key<256; key++) {
+    if (!TT.fixed[key]) continue;
+    for (len = 0, al = TT.fixed[key]; al; al = al->next) len++;
+    last = xmalloc(len*sizeof(void *));
+    for (len = 0, al = TT.fixed[key]; al; al = al->next) last[len++] = al;
+    qsort(last, len, sizeof(void *), (void *)lensort);
+    for (ii = 0; iinext = ii ? last[ii-1] : 0;
+    TT.fixed[key] = last[len-1];
+    free(last);
   }
 }
 
@@ -476,8 +523,7 @@
   if (!TT.A) TT.A = TT.C;
   if (!TT.B) TT.B = TT.C;
 
-  TT.indelim = '\n' * !FLAG(z);
-  TT.outdelim = '\n' * !FLAG(Z);
+  TT.delim = '\n' * !FLAG(z);
 
   // Handle egrep and fgrep
   if (*toys.which->name == 'e') toys.optflags |= FLAG_E;
diff -Nru toybox-0.8.8+dfsg/toys/posix/ln.c toybox-0.8.9+dfsg/toys/posix/ln.c
--- toybox-0.8.8+dfsg/toys/posix/ln.c	2022-08-12 07:58:03.000000000 +0000
+++ toybox-0.8.9+dfsg/toys/posix/ln.c	2023-01-10 19:24:45.000000000 +0000
@@ -52,13 +52,15 @@
   } else buf.st_mode = 0;
 
   for (i=0; itype < 2) {
     char c = TT.buf[(*offset)++];
-    pad += 4;
 
+    pad += 4;
     if (!t->type) {
       c &= 127;
       if (c<=32) sprintf(buf, "%.3s", ascii+(3*c));
@@ -93,8 +93,7 @@
   // Integer types
   } else {
     unsigned long long ll = 0, or;
-    char *c[] = {"%*lld", "%*llu", "%0*llo", "%0*llx"},
-      *class = c[t->type-2];
+    char *c[] = {"%*lld", "%*llu", "%0*llo", "%0*llx"}, *class = c[t->type-2];
 
     // Work out width of field
     if (t->size == 8) {
@@ -126,7 +125,6 @@
 
 static void od_outline(void)
 {
-  unsigned flags = toys.optflags;
   char buf[128], *abases[] = {"", "%07lld", "%07llo", "%06llx"};
   struct odtype *types = (struct odtype *)toybuf;
   int i, j, len, pad;
@@ -134,8 +132,8 @@
   if (TT.leftovernext) append_base(arg->arg);
-  if (toys.optflags & FLAG_b) append_base("o1");
-  if (toys.optflags & FLAG_c) append_base("c");
-  if (toys.optflags & FLAG_d) append_base("u2");
-  if (toys.optflags & FLAG_o) append_base("o2");
-  if (toys.optflags & FLAG_s) append_base("d2");
-  if (toys.optflags & FLAG_x) append_base("x2");
+  if (FLAG(b)) append_base("o1");
+  if (FLAG(c)) append_base("c");
+  if (FLAG(d)) append_base("u2");
+  if (FLAG(o)) append_base("o2");
+  if (FLAG(s)) append_base("d2");
+  if (FLAG(x)) append_base("x2");
   if (!TT.types) append_base("o2");
 
   loopfiles(toys.optargs, do_od);
diff -Nru toybox-0.8.8+dfsg/toys/posix/patch.c toybox-0.8.9+dfsg/toys/posix/patch.c
--- toybox-0.8.8+dfsg/toys/posix/patch.c	2022-08-12 07:58:03.000000000 +0000
+++ toybox-0.8.9+dfsg/toys/posix/patch.c	2023-01-10 19:24:45.000000000 +0000
@@ -449,7 +449,7 @@
 
         if (del) {
           if (!FLAG(s)) printf("removing %s\n", name);
-          xunlink(name);
+          if (!FLAG(dry_run)) xunlink(name);
           state = 0;
         // If we've got a file to open, do so.
         } else if (!FLAG(p) || i <= TT.p) {
@@ -457,8 +457,11 @@
           if ((!strcmp(oldname, "/dev/null") || !oldsum) && access(name, F_OK))
           {
             if (!FLAG(s)) printf("creating %s\n", name);
-            if (mkpath(name)) perror_exit("mkpath %s", name);
-            TT.filein = xcreate(name, O_CREAT|O_EXCL|O_RDWR, 0666);
+            if (FLAG(dry_run)) TT.filein = xopen("/dev/null", O_RDWR);
+            else {
+              if (mkpath(name)) perror_exit("mkpath %s", name);
+              TT.filein = xcreate(name, O_CREAT|O_EXCL|O_RDWR, 0666);
+            }
           } else {
             if (!FLAG(s)) printf("patching %s\n", name);
             TT.filein = xopenro(name);
diff -Nru toybox-0.8.8+dfsg/toys/posix/printf.c toybox-0.8.9+dfsg/toys/posix/printf.c
--- toybox-0.8.8+dfsg/toys/posix/printf.c	2022-08-12 07:58:03.000000000 +0000
+++ toybox-0.8.9+dfsg/toys/posix/printf.c	2023-01-10 19:24:45.000000000 +0000
@@ -23,7 +23,7 @@
 #include "toys.h"
 
 // Detect matching character (return true/false) and advance pointer if match.
-static int eat(char **s, char c)
+static int chrstart(char **s, char c)
 {
   int x = (**s == c);
 
@@ -42,7 +42,7 @@
   if (*ptr == 'c') xexit();
 
   // 0x12 hex escapes have 1-2 digits, \123 octal escapes have 1-3 digits.
-  if (eat(&ptr, 'x')) base = 16;
+  if (chrstart(&ptr, 'x')) base = 16;
   else {
     if (posix && *ptr=='0') ptr++;
     if (*ptr >= '0' && *ptr <= '7') base = 8;
@@ -85,8 +85,8 @@
 
     // Loop through characters in format
     while (*f) {
-      if (eat(&f, '\\')) putchar(handle_slash(&f, 0));
-      else if (!eat(&f, '%') || *f == '%') putchar(*f++);
+      if (chrstart(&f, '\\')) putchar(handle_slash(&f, 0));
+      else if (!chrstart(&f, '%') || *f == '%') putchar(*f++);
 
       // Handle %escape
       else {
@@ -97,10 +97,10 @@
         *to++ = '%';
         while (strchr("-+# '0", *f) && (to-toybuf)<10) *to++ = *f++;
         for (;;) {
-          if (eat(&f, '*')) {
+          if (chrstart(&f, '*')) {
             if (*arg) wp[i] = atolx(*arg++);
           } else while (*f >= '0' && *f <= '9') wp[i] = (wp[i]*10)+(*f++)-'0';
-          if (i++ || !eat(&f, '.')) break;
+          if (i++ || !chrstart(&f, '.')) break;
           wp[1] = 0;
         }
         c = *f++;
@@ -110,7 +110,8 @@
 
         // Output %esc using parsed format string
         if (c == 'b') {
-          while (*aa) putchar(eat(&aa, '\\') ? handle_slash(&aa, 1) : *aa++);
+          while (*aa)
+            putchar(chrstart(&aa, '\\') ? handle_slash(&aa, 1) : *aa++);
 
           continue;
         } else if (c == 'c') printf(toybuf, wp[0], wp[1], *aa);
diff -Nru toybox-0.8.8+dfsg/toys/posix/ps.c toybox-0.8.9+dfsg/toys/posix/ps.c
--- toybox-0.8.8+dfsg/toys/posix/ps.c	2022-08-12 07:58:03.000000000 +0000
+++ toybox-0.8.9+dfsg/toys/posix/ps.c	2023-01-10 19:24:45.000000000 +0000
@@ -845,7 +845,7 @@
     off_t temp = 6;
 
     sprintf(buf, "%lld/exe", slot[SLOT_tid]);
-    if (readfileat(fd, buf, buf, &temp) && !memcmp(buf, "\177ELF", 4)) {
+    if (readfileat(fd, buf, buf, &temp) && !smemcmp(buf, "\177ELF", 4)) {
       if (buf[4] == 1) slot[SLOT_bits] = 32;
       else if (buf[4] == 2) slot[SLOT_bits] = 64;
     }
diff -Nru toybox-0.8.8+dfsg/toys/posix/rm.c toybox-0.8.9+dfsg/toys/posix/rm.c
--- toybox-0.8.8+dfsg/toys/posix/rm.c	2022-08-12 07:58:03.000000000 +0000
+++ toybox-0.8.9+dfsg/toys/posix/rm.c	2023-01-10 19:24:45.000000000 +0000
@@ -4,7 +4,7 @@
  *
  * See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/rm.html
 
-USE_RM(NEWTOY(rm, "fiRrv[-fi]", TOYFLAG_BIN))
+USE_RM(NEWTOY(rm, "f(force)iRrv[-fi]", TOYFLAG_BIN))
 
 config RM
   bool "rm"
diff -Nru toybox-0.8.8+dfsg/toys/posix/sed.c toybox-0.8.9+dfsg/toys/posix/sed.c
--- toybox-0.8.8+dfsg/toys/posix/sed.c	2022-08-12 07:58:03.000000000 +0000
+++ toybox-0.8.9+dfsg/toys/posix/sed.c	2023-01-10 19:24:45.000000000 +0000
@@ -4,6 +4,8 @@
  *
  * See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/sed.html
  *
+ * xform See https://www.gnu.org/software/tar/manual/html_section/transform.html
+ *
  * TODO: lines > 2G could wrap signed int length counters. Not just getline()
  * but N and s///
  * TODO: make y// handle unicode, unicode delimiters
@@ -12,12 +14,15 @@
  * test '//q' with no previous regex, also repeat previous regex?
  *
  * Deviations from POSIX: allow extended regular expressions with -r,
- * editing in place with -i, separate with -s, NUL-separated input with -z,
+ * editing in place with -i, separate with -s, NUL-delimited strings with -z,
  * printf escapes in text, line continuations, semicolons after all commands,
  * 2-address anywhere an address is allowed, "T" command, multiline
  * continuations for [abc], \; to end [abc] argument before end of line.
+ * Explicit violations of stuff posix says NOT to do: N at EOF does default
+ * print, l escapes \n
+ * Added --tarxform mode to support tar --xform
 
-USE_SED(NEWTOY(sed, "(help)(version)e*f*i:;nErz(null-data)s[+Er]", TOYFLAG_BIN|TOYFLAG_LOCALE|TOYFLAG_NOHELP))
+USE_SED(NEWTOY(sed, "(help)(version)(tarxform)e*f*i:;nErz(null-data)s[+Er]", TOYFLAG_BIN|TOYFLAG_LOCALE|TOYFLAG_NOHELP))
 
 config SED
   bool "sed"
@@ -74,8 +79,8 @@
       G  Get remembered line (appending to current line)
       h  Remember this line (overwriting remembered line)
       H  Remember this line (appending to remembered line, if any)
-      l  Print line escaping \abfrtv (but not \n), octal escape other nonprintng
-         chars, wrap lines to terminal width with \, append $ to end of line.
+      l  Print line escaping \abfrtvn, octal escape other nonprintng chars,
+         wrap lines to terminal width with \, append $ to end of line.
       n  Print default output and read next line over current line (quit at EOF)
       N  Append \n and next line of input to this line. Quit at EOF without
          default output. Advances line counter for ADDRESS and "=".
@@ -126,12 +131,12 @@
   // processed pattern list
   struct double_list *pattern;
 
-  char *nextline, *remember;
+  char *nextline, *remember, *tarxform;
   void *restart, *lastregex;
   long nextlen, rememberlen, count;
   int fdout, noeol;
-  unsigned xx;
-  char delim;
+  unsigned xx, tarxlen, xflags;
+  char delim, xftype;
 )
 
 // Linked list of parsed sed commands. Offset fields indicate location where
@@ -147,21 +152,38 @@
   int rmatch[2];  // offset of regex struct for prefix matches (/abc/,/def/p)
   int arg1, arg2, w; // offset of two arguments per command, plus s//w filename
   unsigned not, hit;
-  unsigned sflags; // s///flag bits: i=1, g=2, p=4, x=8
+  unsigned sflags; // s///flag bits, see SFLAG macros below
   char c; // action
 };
 
+#define SFLAG_i 1
+#define SFLAG_g 2
+#define SFLAG_p 4
+#define SFLAG_x 8
+#define SFLAG_slash 16
+#define SFLAG_R 32
+#define SFLAG_S 64
+#define SFLAG_H 128
+
 // Write out line with potential embedded NUL, handling eol/noeol
 static int emit(char *line, long len, int eol)
 {
-  int l, old = line[len];
+  int l = len, old = line[len];
 
-  if (TT.noeol && !writeall(TT.fdout, "\n", 1)) return 1;
+  if (FLAG(tarxform)) {
+    TT.tarxform = xrealloc(TT.tarxform, TT.tarxlen+len+TT.noeol+eol);
+    if (TT.noeol) TT.tarxform[TT.tarxlen++] = TT.delim;
+    memcpy(TT.tarxform+TT.tarxlen, line, len);
+    TT.tarxlen += len;
+    if (eol) TT.tarxform[TT.tarxlen++] = TT.delim;
+  } else {
+    if (TT.noeol && !writeall(TT.fdout, &TT.delim, 1)) return 1;
+    if (eol) line[len++] = TT.delim;
+    if (!len) return 0;
+    l = writeall(TT.fdout, line, len);
+    if (eol) line[len-1] = old;
+  }
   TT.noeol = !eol;
-  if (eol) line[len++] = '\n';
-  if (!len) return 0;
-  l = writeall(TT.fdout, line, len);
-  if (eol) line[len-1] = old;
   if (l != len) {
     if (TT.fdout != 1) perror_msg("short write");
 
@@ -180,7 +202,7 @@
 
   if (newline) newlen = -newlen;
   s = *old = xrealloc(*old, oldlen+newlen+newline+1);
-  if (newline) s[oldlen++] = '\n';
+  if (newline) s[oldlen++] = TT.delim;
   memcpy(s+oldlen, new, newlen);
   s[oldlen+newlen] = 0;
 
@@ -206,32 +228,52 @@
     int file;
     char *str;
   } *append = 0;
-  char *line = TT.nextline;
-  long len = TT.nextlen;
+  char *line;
+  long len;
   struct sedcmd *command;
   int eol = 0, tea = 0;
 
-  // Ignore EOF for all files before last unless -i
-  if (!pline && !FLAG(i) && !FLAG(s)) return;
+  if (FLAG(tarxform)) {
+    if (!pline) return;
 
-  // Grab next line for deferred processing (EOF detection: we get a NULL
-  // pline at EOF to flush last line). Note that only end of _last_ input
-  // file matches $ (unless we're doing -i).
-  TT.nextline = 0;
-  TT.nextlen = 0;
-  if (pline) {
-    TT.nextline = *pline;
-    TT.nextlen = plen;
+    line = *pline;
+    len = plen;
     *pline = 0;
+    pline = 0;
+  } else {
+    line = TT.nextline;
+    len = TT.nextlen;
+
+    // Ignore EOF for all files before last unless -i or -s
+    if (!pline && !FLAG(i) && !FLAG(s)) return;
+
+    // Grab next line for deferred processing (EOF detection: we get a NULL
+    // pline at EOF to flush last line). Note that only end of _last_ input
+    // file matches $ (unless we're doing -i).
+    TT.nextline = 0;
+    TT.nextlen = 0;
+    if (pline) {
+      TT.nextline = *pline;
+      TT.nextlen = plen;
+      *pline = 0;
+    }
   }
 
   if (!line || !len) return;
-  if (line[len-1] == '\n') line[--len] = eol++;
+  if (line[len-1] == TT.delim) line[--len] = eol++;
+  if (FLAG(tarxform) && len) {
+    TT.xftype = line[--len];
+    line[len] = 0;
+  }
   TT.count++;
 
-  // The restart-1 is because we added one to make sure it wasn't NULL,
-  // otherwise N as last command would restart script
-  command = TT.restart ? ((struct sedcmd *)TT.restart)-1 : (void *)TT.pattern;
+  // To prevent N as last command from restarting script, we added 1 to restart
+  // so we'd use it here even when NULL. Alas, compilers that think C has
+  // references instead of pointers assume ptr-1 can never be NULL (demonstrably
+  // untrue) and inappropriately dead code eliminate, so use LP64 math until
+  // we get a -fpointers-are-not-references compiler option.
+  command = (void *)(TT.restart ? ((unsigned long)TT.restart)-1
+    : (unsigned long)TT.pattern);
   TT.restart = 0;
 
   while (command) {
@@ -328,7 +370,7 @@
     } else if (c=='D') {
       // Delete up to \n or end of buffer
       str = line;
-      while ((str-line)= ' ') toybuf[off++] = line[i];
         else off += sprintf(toybuf+off, "\\%03o", line[i]);
       }
       toybuf[off++] = '$';
       emit(toybuf, off, 1);
     } else if (c=='n') {
-      TT.restart = command->next+1;
+      // The +1 forces restart processing even when next is null
+      TT.restart = (void *)(((unsigned long)command->next)+1);
 
       break;
     } else if (c=='N') {
       // Can't just grab next line because we could have multiple N and
       // we need to actually read ahead to get N;$p EOF detection right.
       if (pline) {
-        TT.restart = command->next+1;
+        // The +1 forces restart processing even when  next is null
+        TT.restart = (void *)(((unsigned long)command->next)+1);
         extend_string(&line, TT.nextline, len, -TT.nextlen);
         free(TT.nextline);
         TT.nextline = line;
@@ -407,7 +451,7 @@
       // Pending append goes out right after N
       goto done; 
     } else if (c=='p' || c=='P') {
-      char *l = (c=='P') ? strchr(line, '\n') : 0;
+      char *l = (c=='P') ? strchr(line, TT.delim) : 0;
 
       if (emit(line, l ? l-line : len, eol)) break;
     } else if (c=='q' || c=='Q') {
@@ -425,14 +469,24 @@
       regmatch_t *match = (void *)toybuf;
       regex_t *reg = get_regex(command, command->arg1);
       int mflags = 0, count = 0, l2used = 0, zmatch = 1, l2l = len, l2old = 0,
-        mlen, off, newlen;
+        bonk = 0, mlen, off, newlen;
+
+      // Skip suppressed --tarxform types
+      if (TT.xftype && (command->sflags & (SFLAG_R<sflags & SFLAG_slash) && mlen==len) {
+          while (len && ++bonk && line[--len]=='/');
+          continue;
+        }
+
         mflags = REG_NOTBOL;
 
         // Zero length matches don't count immediately after a previous match
-        mlen = match[0].rm_eo-match[0].rm_so;
         if (!mlen && !zmatch) {
           if (rline-line == len) break;
           l2[l2used++] = *rline++;
@@ -441,7 +495,7 @@
         } else zmatch = 0;
 
         // If we're replacing only a specific match, skip if this isn't it
-        off = command->sflags>>4;
+        off = command->sflags>>8;
         if (off && off != ++count) {
           if (l2) memcpy(l2+l2used, rline, match[0].rm_eo);
           l2used += match[0].rm_eo;
@@ -503,9 +557,9 @@
         l2used += newlen;
         rline += match[0].rm_eo;
 
-        // Stop after first substitution unless we have flag g
-        if (!(command->sflags & 2)) break;
+        if (!(command->sflags & SFLAG_g)) break;
       }
+      len += bonk;
 
       // If we made any changes, finish off l2 and swap it for line
       if (l2) {
@@ -518,8 +572,7 @@
       }
 
       if (mflags) {
-        // flag p
-        if (command->sflags & 4) emit(line, len, eol);
+        if (command->sflags & SFLAG_p) emit(line, len, eol);
 
         tea = 1;
         if (command->w) goto writenow;
@@ -529,6 +582,8 @@
       char *name;
 
 writenow:
+      if (FLAG(tarxform)) error_exit("tilt");
+
       // Swap out emit() context
       fd = TT.fdout;
       noeol = TT.noeol;
@@ -572,9 +627,10 @@
     command = command->next;
   }
 
+done:
   if (line && !FLAG(n)) emit(line, len, eol);
 
-done:
+  // TODO: should "sed -z ax" use \n instead of NUL?
   if (dlist_terminate(append)) while (append) {
     struct append *a = append->next;
 
@@ -583,7 +639,7 @@
 
       // Force newline if noeol pending
       if (fd != -1) {
-        if (TT.noeol) xwrite(TT.fdout, "\n", 1);
+        if (TT.noeol) xwrite(TT.fdout, &TT.delim, 1);
         TT.noeol = 0;
         xsendfile(fd, TT.fdout);
         close(fd);
@@ -594,6 +650,12 @@
     append = a;
   }
   free(line);
+
+  if (TT.tarxlen) {
+    dprintf(TT.fdout, "%08x", --TT.tarxlen);
+    writeall(TT.fdout, TT.tarxform, TT.tarxlen);
+    TT.tarxlen = 0;
+  }
 }
 
 // Callback called on each input file
@@ -736,6 +798,16 @@
     }
     if (!*line) return;
 
+    if (FLAG(tarxform) && strstart(&line, "flags=")) {
+      TT.xflags = 7;
+      while (0<=(i = stridx("rRsShH", *line))) {
+        if (i&1) TT.xflags |= 1<<(i>>1);
+        else TT.xflags &= ~(1<<(i>>1));
+        line++;
+      }
+      continue;
+    }
+
     // Start by writing data into toybuf.
 
     errstart = line;
@@ -839,27 +911,36 @@
       i = command->arg1;
       command->arg1 = command->arg2;
       command->arg2 = i;
+      command->sflags = TT.xflags*SFLAG_R;
 
       // get flags
       for (line++; *line; line++) {
         long l;
 
         if (isspace(*line) && *line != '\n') continue;
-
         if (0 <= (l = stridx("igpx", *line))) command->sflags |= 1<sflags |= 1<<0;
-        else if (!(command->sflags>>4) && 0<(l = strtol(line, &line, 10))) {
-          command->sflags |= l << 4;
+        else if (FLAG(tarxform) && 0 <= (l = stridx("RSH", *line)))
+          command->sflags |= SFLAG_R<sflags &= ~(SFLAG_R<sflags>>8) && 0<(l = strtol(line, &line, 10))) {
+          command->sflags |= l << 8;
           line--;
         } else break;
       }
-      flags = (FLAG(r) || (command->sflags&8)) ? REG_EXTENDED : 0;
-      if (command->sflags&1) flags |= REG_ICASE;
+      flags = (FLAG(r) || (command->sflags & SFLAG_x)) ? REG_EXTENDED : 0;
+      if (command->sflags & SFLAG_i) flags |= REG_ICASE;
 
       // We deferred actually parsing the regex until we had the s///i flag
       // allocating the space was done by extend_string() above
       if (!*TT.remember) command->arg1 = 0;
-      else xregcomp((void *)(command->arg1+(char *)command),TT.remember,flags);
+      else {
+        xregcomp((void *)(command->arg1+(char *)command), TT.remember, flags);
+        if (FLAG(tarxform) && TT.remember[strlen(TT.remember)-1]=='/')
+          command->sflags |= SFLAG_slash;
+      }
       free(TT.remember);
       TT.remember = 0;
       if (*line == 'w') {
@@ -980,11 +1061,17 @@
   error_exit("bad pattern '%s'@%ld (%c)", errstart, line-errstart+1L, *line);
 }
 
+// Is the pointer "find" within the string "range".
+static int instr(char *find, char *range)
+{
+  return find>=range && range+strlen(range)>=find;
+}
+
 void sed_main(void)
 {
-  struct arg_list *al;
-  char **args = toys.optargs;
+  char **args = toys.optargs, **aa;
 
+  if (FLAG(tarxform)) toys.optflags |= FLAG_z;
   if (!FLAG(z)) TT.delim = '\n';
 
   // Lie to autoconf when it asks stupid questions, so configure regexes
@@ -1006,13 +1093,18 @@
     (TT.e = xzalloc(sizeof(struct arg_list)))->arg = *(args++);
   }
 
-  // Option parsing infrastructure can't interlace "-e blah -f blah -e blah"
-  // so handle all -e, then all -f. (At least the behavior's consistent.)
-
-  for (al = TT.e; al; al = al->next) parse_pattern(&al->arg, strlen(al->arg));
+  // -e and -f care about order, so use argv[] to recreate original order
+  for (aa = toys.argv+1; *aa; aa++) {
+    if (TT.e && instr(TT.e->arg, *aa)) {
+      parse_pattern(&TT.e->arg, strlen(TT.e->arg));
+      free(llist_pop(&TT.e));
+    }
+    if (TT.f && instr(TT.f->arg, *aa)) {
+      do_lines(xopenro(TT.f->arg), TT.delim, parse_pattern);
+      free(llist_pop(&TT.f));
+    }
+  }
   parse_pattern(0, 0);
-  for (al = TT.f; al; al = al->next)
-    do_lines(xopenro(al->arg), TT.delim, parse_pattern);
   dlist_terminate(TT.pattern);
   if (TT.nextlen) error_exit("no }");  
 
diff -Nru toybox-0.8.8+dfsg/toys/posix/sleep.c toybox-0.8.9+dfsg/toys/posix/sleep.c
--- toybox-0.8.8+dfsg/toys/posix/sleep.c	2022-08-12 07:58:03.000000000 +0000
+++ toybox-0.8.9+dfsg/toys/posix/sleep.c	2023-01-10 19:24:45.000000000 +0000
@@ -24,7 +24,10 @@
 void sleep_main(void)
 {
   struct timespec ts;
+  char **args;
 
-  xparsetimespec(*toys.optargs, &ts);
-  toys.exitval = !!nanosleep(&ts, NULL);
+  for (args = toys.optargs; !toys.exitval && *args; args++) {
+    xparsetimespec(*args, &ts);
+    toys.exitval = !!nanosleep(&ts, NULL);
+  }
 }
diff -Nru toybox-0.8.8+dfsg/toys/posix/sort.c toybox-0.8.9+dfsg/toys/posix/sort.c
--- toybox-0.8.8+dfsg/toys/posix/sort.c	2022-08-12 07:58:03.000000000 +0000
+++ toybox-0.8.9+dfsg/toys/posix/sort.c	2023-01-10 19:24:45.000000000 +0000
@@ -7,7 +7,7 @@
  * Deviations from POSIX: Lots.
  * We invented -x
 
-USE_SORT(NEWTOY(sort, USE_SORT_FLOAT("g")"S:T:m" "o:k*t:" "xVbMcszdfirun", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_ARGFAIL(2)))
+USE_SORT(NEWTOY(sort, USE_SORT_FLOAT("g")"S:T:m" "o:k*t:" "xVbMCcszdfirun", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_ARGFAIL(2)))
 
 config SORT
   bool "sort"
@@ -21,7 +21,8 @@
     -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	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)
     -i	Ignore nonprinting characters
@@ -282,9 +283,12 @@
   *pline = 0;
 
   // handle -c here so we don't allocate more memory than necessary.
-  if (FLAG(c)) {
-    if (TT.lines && compare_keys((void *)&TT.lines, &line)>-!!FLAG(u))
-      error_exit("%s: Check line %u\n", TT.name, TT.linecount);
+  if (FLAG(C)||FLAG(c)) {
+    if (TT.lines && compare_keys((void *)&TT.lines, &line)>-!!FLAG(u)) {
+      toys.exitval = 1;
+      if (FLAG(C)) xexit();
+      error_exit("%s: Check line %u", TT.name, TT.linecount+1);
+    }
     free(TT.lines);
     TT.lines = (void *)line;
   } else {
@@ -299,7 +303,7 @@
 static void sort_read(int fd, char *name)
 {
   TT.name = name;
-  do_lines(fd, FLAG(z) ? '\0' : '\n', sort_lines);
+  do_lines(fd, '\n'*!FLAG(z), sort_lines);
 }
 
 void sort_main(void)
@@ -339,10 +343,9 @@
           flag = 1<<(optlist-temp2+strlen(optlist)-1);
 
           // Was it a flag that can apply to a key?
-          if (!temp2 || flag>FLAG_x || (flag&(FLAG_u|FLAG_c|FLAG_s|FLAG_z))) {
-            toys.exitval = 2;
+          if (!temp2 || flag>FLAG_x || (flag&(FLAG_u|FLAG_c|FLAG_s|FLAG_z)))
             error_exit("Unknown key option.");
-          }
+
           // b after , means strip _trailing_ space, not leading.
           if (idx && flag==FLAG_b) flag = FLAG_bb;
           key->flags |= flag;
@@ -362,7 +365,7 @@
 
   // The compare (-c) logic was handled in sort_read(),
   // so if we got here, we're done.
-  if (FLAG(c)) goto exit_now;
+  if (FLAG(C)||FLAG(c)) goto exit_now;
 
   // Perform the actual sort
   qsort(TT.lines, TT.linecount, sizeof(char *), compare_keys);
diff -Nru toybox-0.8.8+dfsg/toys/posix/tail.c toybox-0.8.9+dfsg/toys/posix/tail.c
--- toybox-0.8.8+dfsg/toys/posix/tail.c	2022-08-12 07:58:03.000000000 +0000
+++ toybox-0.8.9+dfsg/toys/posix/tail.c	2023-01-10 19:24:45.000000000 +0000
@@ -289,8 +289,8 @@
   if (!FLAG(n) && !FLAG(c)) {
     char *arg = *args;
 
-    // handle old "-42" style arguments, else default to last 10 lines
-    if (arg && *arg == '-' && arg[1]) {
+    // handle old "-42" / "+42" style arguments, else default to last 10 lines
+    if (arg && (*arg == '-' || *arg == '+') && arg[1]) {
       TT.n = atolx(*(args++));
       toys.optc--;
     } else TT.n = -10;
diff -Nru toybox-0.8.8+dfsg/toys/posix/tar.c toybox-0.8.9+dfsg/toys/posix/tar.c
--- toybox-0.8.8+dfsg/toys/posix/tar.c	2022-08-12 07:58:03.000000000 +0000
+++ toybox-0.8.9+dfsg/toys/posix/tar.c	2023-01-10 19:24:45.000000000 +0000
@@ -61,7 +61,7 @@
   struct double_list *incl, *excl, *seen;
   struct string_list *dirs;
   char *cwd, **xfsed;
-  int fd, ouid, ggid, hlc, warn, sparselen, pid;
+  int fd, ouid, ggid, hlc, warn, sparselen, pid, xfpipe[2];
   struct dev_ino archive_di;
   long long *sparse;
   time_t mtt;
@@ -192,6 +192,23 @@
   (*b)[len] = 0;
 }
 
+static char *xform(char **name, char type)
+{
+  char buf[9], *end;
+  off_t len;
+
+  if (!TT.xform) return 0;
+
+  buf[8] = 0;
+  if (dprintf(TT.xfpipe[0], "%s%c%c", *name, type, 0) != strlen(*name)+2
+    || readall(TT.xfpipe[1], buf, 8) != 8
+    || !(len = estrtol(buf, &end, 16)) || errno ||*end) error_exit("bad xform");
+  xreadall(TT.xfpipe[1], *name = xmalloc(len+1), len);
+  (*name)[len] = 0;
+
+  return *name;
+}
+
 // callback from dirtree to create archive
 static int add_to_tar(struct dirtree *node)
 {
@@ -219,20 +236,23 @@
   }
 
   // Consume the 1 extra byte alocated in dirtree_path()
-  if (S_ISDIR(st->st_mode) && name[i-1] != '/') strcat(name, "/");
+  if (S_ISDIR(st->st_mode) && lnk[-1] != '/') strcpy(lnk, "/");
 
   // remove leading / and any .. entries from saved name
-  if (!FLAG(P)) while (*hname == '/') hname++;
-  for (lnk = hname;;) {
-    if (!(lnk = strstr(lnk, ".."))) break;
-    if (lnk == hname || lnk[-1] == '/') {
-      if (!lnk[2]) goto done;
-      if (lnk[2]=='/') {
-        lnk = hname = lnk+3;
-        continue;
+  if (!FLAG(P)) {
+    while (*hname == '/') hname++;
+    for (lnk = hname;;) {
+      if (!(lnk = strstr(lnk, ".."))) break;
+      if (lnk == hname || lnk[-1] == '/') {
+        if (!lnk[2]) goto done;
+        if (lnk[2]=='/') {
+          lnk = hname = lnk+3;
+          continue;
+        }
       }
+      lnk += 2;
     }
-    lnk += 2;
+    if (!*hname) hname = "./";
   }
   if (!*hname) goto done;
 
@@ -242,17 +262,12 @@
     TT.warn = 0;
   }
 
-  // Note: linux sed doesn't add newline, so no need to remove it or use -z.
-  if (TT.xfsed)
-    if (!(hname = xfname = xrunread(TT.xfsed, hname))) error_exit("bad xform");
-
+  // Override dentry data from command line and fill out header data
   if (TT.owner) st->st_uid = TT.ouid;
   if (TT.group) st->st_gid = TT.ggid;
   if (TT.mode) st->st_mode = string_to_mode(TT.mode, st->st_mode);
   if (TT.mtime) st->st_mtime = TT.mtt;
-
   memset(&hdr, 0, sizeof(hdr));
-  strncpy(hdr.name, hname, sizeof(hdr.name));
   ITOO(hdr.mode, st->st_mode &07777);
   ITOO(hdr.uid, st->st_uid);
   ITOO(hdr.gid, st->st_gid);
@@ -260,16 +275,13 @@
   ITOO(hdr.mtime, st->st_mtime);
   strcpy(hdr.magic, "ustar  ");
 
-  // Hard link or symlink? i=0 neither, i=1 hardlink, i=2 symlink
-
   // Are there hardlinks to a non-directory entry?
+  lnk = 0;
   if (st->st_nlink>1 && !S_ISDIR(st->st_mode)) {
     // Have we seen this dev&ino before?
     for (i = 0; ist_ino;
       TT.hlx[TT.hlc].di.dev = st->st_dev;
       TT.hlc++;
-      i = 0;
     }
-  } else i = 0;
+  }
 
-  // Handle file types
-  if (i || S_ISLNK(st->st_mode)) {
-    hdr.type = '1'+!i;
-    if (!i && !(lnk = xreadlink(name))) {
+  xfname = xform(&hname, 'r');
+  strncpy(hdr.name, hname, sizeof(hdr.name));
+
+  // Handle file types: 0=reg, 1=hardlink, 2=sym, 3=chr, 4=blk, 5=dir, 6=fifo
+  if (lnk || S_ISLNK(st->st_mode)) {
+    hdr.type = '1'+!lnk;
+    if (lnk) {
+      if (!xform(&lnk, 'h')) lnk = xstrdup(lnk);
+    } else if (!(lnk = xreadlink(name))) {
       perror_msg("readlink");
       goto done;
-    }
+    } else xform(&lnk, 's');
+
     maybe_prefix_block(lnk, sizeof(hdr.link), 'K');
     strncpy(hdr.link, lnk, sizeof(hdr.link));
-    if (!i) free(lnk);
+    free(lnk);
   } else if (S_ISREG(st->st_mode)) {
     hdr.type = '0';
     ITOO(hdr.size, st->st_size);
@@ -781,11 +798,11 @@
 
     // We accept --show-transformed but always do, so it's a NOP.
     name = TT.hdr.name;
-    if (TT.xfsed) {
-      if (!(name = xrunread(TT.xfsed, name))) error_exit("bad xform");
+    if (xform(&name, 'r')) {
       free(TT.hdr.name);
       TT.hdr.name = name;
     }
+    if ((i = "\0hs"[stridx("12", tar.type)+1])) xform(&TT.hdr.link_target, i);
 
     for (i = 0; itm_mday, lc->tm_hour, lc->tm_min, FLAG(full_time) ? perm : "");
       }
       printf("%s", name);
-      if (TT.hdr.link_target) printf(" -> %s", TT.hdr.link_target);
+      if (TT.hdr.link_target)
+        printf(" %s %s", tar.type=='2' ? "->" : "link to", TT.hdr.link_target);
       xputc('\n');
       skippy(TT.hdr.size);
     } else {
@@ -868,7 +886,7 @@
 
   dlist_add(list, n);
   if (i && n[i-1]=='\n') i--;
-  while (i && n[i-1] == '/') i--;
+  while (i>1 && n[i-1] == '/') i--;
   n[i] = 0;
 }
 
@@ -878,10 +896,14 @@
   if (pline) trim2list(TT.X ? &TT.excl : &TT.incl, *pline);
 }
 
+static  char *get_archiver()
+{
+  return FLAG(I) ? TT.I : FLAG(z) ? "gzip" : FLAG(j) ? "bzip2" : "xz";
+}
+
 void tar_main(void)
 {
-  char *s, **args = toys.optargs,
-    *archiver = FLAG(I) ? TT.I : (FLAG(z) ? "gzip" : (FLAG(J) ? "xz":"bzip2"));
+  char *s, **xfsed, **args = toys.optargs;
   int len = 0, ii;
 
   // Needed when extracting to command
@@ -923,13 +945,17 @@
     struct arg_list *al;
 
     for (ii = 0, al = TT.xform; al; al = al->next) ii++;
-    TT.xfsed = xmalloc((ii+1)*2*sizeof(char *));
-    TT.xfsed[0] = "sed";
-    for (ii = 1, al = TT.xform; al; al = al->next) {
-      TT.xfsed[ii++] = "-e";
-      TT.xfsed[ii++] = al->arg;
-    }
-    TT.xfsed[ii] = 0;
+    xfsed = xmalloc((ii+2)*2*sizeof(char *));
+    xfsed[0] = "sed";
+    xfsed[1] = "--tarxform";
+    for (ii = 2, al = TT.xform; al; al = al->next) {
+      xfsed[ii++] = "-e";
+      xfsed[ii++] = al->arg;
+    }
+    xfsed[ii] = 0;
+    TT.xfpipe[0] = TT.xfpipe[1] = -1;
+    xpopen_both(xfsed, TT.xfpipe);
+    free(xfsed);
   }
 
   // nommu reentry for nonseekable input skips this, parent did it for us
@@ -964,7 +990,7 @@
       if (len!=512 || !is_tar_header(hdr)) {
         // detect gzip and bzip signatures
         if (SWAP_BE16(*(short *)hdr)==0x1f8b) toys.optflags |= FLAG_z;
-        else if (!memcmp(hdr, "BZh", 3)) toys.optflags |= FLAG_j;
+        else if (!smemcmp(hdr, "BZh", 3)) toys.optflags |= FLAG_j;
         else if (peek_be(hdr, 7) == 0xfd377a585a0000UL) toys.optflags |= FLAG_J;
         else error_exit("Not tar");
 
@@ -976,11 +1002,11 @@
     if (FLAG(j)||FLAG(z)||FLAG(I)||FLAG(J)) {
       int pipefd[2] = {hdr ? -1 : TT.fd, -1}, i, pid;
       struct string_list *zcat = FLAG(I) ? 0 : find_in_path(getenv("PATH"),
-        FLAG(j) ? "bzcat" : FLAG(J) ? "xzcat" : "zcat");
+        FLAG(z) ? "zcat" : FLAG(j) ? "bzcat" : "xzcat");
 
       // Toybox provides more decompressors than compressors, so try them first
       TT.pid = xpopen_both(zcat ? (char *[]){zcat->str, 0} :
-        (char *[]){archiver, "-d", 0}, pipefd);
+        (char *[]){get_archiver(), "-d", 0}, pipefd);
       if (CFG_TOYBOX_FREE) llist_traverse(zcat, free);
 
       if (!hdr) {
@@ -1052,7 +1078,7 @@
     if (FLAG(j)||FLAG(z)||FLAG(I)||FLAG(J)) {
       int pipefd[2] = {-1, TT.fd};
 
-      xpopen_both((char *[]){archiver, 0}, pipefd);
+      xpopen_both((char *[]){get_archiver(), 0}, pipefd);
       close(TT.fd);
       TT.fd = pipefd[0];
     }
diff -Nru toybox-0.8.8+dfsg/toys/posix/test.c toybox-0.8.9+dfsg/toys/posix/test.c
--- toybox-0.8.8+dfsg/toys/posix/test.c	2022-08-12 07:58:03.000000000 +0000
+++ toybox-0.8.9+dfsg/toys/posix/test.c	2023-01-10 19:24:45.000000000 +0000
@@ -93,6 +93,7 @@
   if (*count>=2 && *s == '-' && s[1] && !s[2]) {
     *count = 2;
     c = s[1];
+    if (c=='a') c = 'e';
     if (-1 != (i = stridx("hLbcdefgkpSusxwr", c))) {
       struct stat st;
 
diff -Nru toybox-0.8.8+dfsg/toys/posix/uname.c toybox-0.8.9+dfsg/toys/posix/uname.c
--- toybox-0.8.8+dfsg/toys/posix/uname.c	2022-08-12 07:58:03.000000000 +0000
+++ toybox-0.8.9+dfsg/toys/posix/uname.c	2023-01-10 19:24:45.000000000 +0000
@@ -4,7 +4,7 @@
  *
  * See http://opengroup.org/onlinepubs/9699919799/utilities/uname.html
 
-USE_UNAME(NEWTOY(uname, "aomvrns", TOYFLAG_BIN))
+USE_UNAME(NEWTOY(uname, "paomvrns", TOYFLAG_BIN))
 USE_ARCH(NEWTOY(arch, 0, TOYFLAG_USR|TOYFLAG_BIN))
 USE_LINUX32(NEWTOY(linux32, 0, TOYFLAG_USR|TOYFLAG_BIN))
 
@@ -60,6 +60,7 @@
     }
     xputsn(c);
   }
+  if (FLAG(p)) xputsn(" unknown"+!needspace);
   xputc('\n');
 }
 
diff -Nru toybox-0.8.8+dfsg/toys/posix/xargs.c toybox-0.8.9+dfsg/toys/posix/xargs.c
--- toybox-0.8.8+dfsg/toys/posix/xargs.c	2022-08-12 07:58:03.000000000 +0000
+++ toybox-0.8.9+dfsg/toys/posix/xargs.c	2023-01-10 19:24:45.000000000 +0000
@@ -9,7 +9,7 @@
  * TODO: -L	Max number of lines of input per command
  * TODO: -x	Exit if can't fit everything in one command
 
-USE_XARGS(NEWTOY(xargs, "^E:P#<0=1optrn#<1(max-args)s#0[!0E]", TOYFLAG_USR|TOYFLAG_BIN))
+USE_XARGS(NEWTOY(xargs, "^E:P#<0(null)=1optr(no-run-if-empty)n#<1(max-args)s#0[!0E]", TOYFLAG_USR|TOYFLAG_BIN))
 
 config XARGS
   bool "xargs"
diff -Nru toybox-0.8.8+dfsg/toys.h toybox-0.8.9+dfsg/toys.h
--- toybox-0.8.8+dfsg/toys.h	2022-08-12 07:58:03.000000000 +0000
+++ toybox-0.8.9+dfsg/toys.h	2023-01-10 19:24:45.000000000 +0000
@@ -134,5 +134,5 @@
 #ifndef TOYBOX_VENDOR
 #define TOYBOX_VENDOR ""
 #endif
-#define TOYBOX_VERSION "0.8.8"TOYBOX_VENDOR
+#define TOYBOX_VERSION "0.8.9"TOYBOX_VENDOR
 #endif
diff -Nru toybox-0.8.8+dfsg/www/faq.html toybox-0.8.9+dfsg/www/faq.html
--- toybox-0.8.8+dfsg/www/faq.html	2022-08-12 07:58:03.000000000 +0000
+++ toybox-0.8.9+dfsg/www/faq.html	2023-01-10 19:24:45.000000000 +0000
@@ -521,7 +521,7 @@
 

Q: How do I cross compile toybox?

A: You need a compiler "toolchain" capable of producing binaries that -run on your target. A toolchain is an +run on your target. A toolchain is an integrated suite of compiler, assembler, and linker, plus the standard headers and libraries necessary to build C programs. (And a few miscellaneous binaries like @@ -548,17 +548,21 @@ LDFLAGS=--static CROSS_COMPILE=m68k-linux-musl- make distclean defconfig toybox

-

(Both of those examples use static linking so you can install just +

Both of those examples use static linking so you can install just the single file to target, or test them with "qemu-m68k toybox". Feel free to dynamically link instead if you prefer, mkroot offers a "dynamic" add-on to copy the compiler's shared libraries into the new root -filesystem.)

+filesystem.

+ +

Although you can individually override $CC and $STRIP and such, +providing the prefix twice applies it twice, ala +"CROSS_COMPILE=prefix- CC=prefix-cc" gives "prefix-prefix-cc".

Toybox's system builder can use a simpler $CROSS -variable to specify the target(s) to build for if you've installed +variable to specify the target name(s) to build for if you've installed compatible cross compilers under the "ccc" directory. -Behind the scenes this uses wildcard expansion to set $CROSS_COMPILER to -an appropriate path/prefix-.

+Behind the scenes this uses wildcard expansion to set $CROSS_COMPILE to +an appropriate "path/prefix-".


Q: What architectures does toybox support?

@@ -717,7 +721,9 @@ Booting a simple system to a shell prompt requires a kernel to drive the hardware (such as Linux, or BSD with a Linux emulation layer), programs for the system to run (such as toybox's commands), and a C library ("libc") to connect them together.

Toybox has a policy of requiring no external dependencies other than the -kernel and C library (at least for defconfig builds). You can optionally enable support for +kernel and C library (at least for defconfig builds). Our "software bill +of materials" (SBOM) defaults to just "the C library", both at build time +and and runtime. You can optionally enable support for additional libraries in menuconfig (such as openssl, zlib, or selinux), but toybox either provides its own built-in versions of such functionality (which the libraries provide larger, more complex, often assembly optimized @@ -757,12 +763,16 @@ root". To enter the resulting root filesystem, "sudo chroot root/host/fs /init". Type "exit" to get back out.

-

You can cross compile simple three package (toybox+libc+linux) -systems configured to boot to a shell prompt under the emulator -qemu -by specifying a target type with CROSS= -(or by setting CROSS_COMPILE= to a cross compiler prefix with optional absolute -path), and pointing the build at a Linux kernel source directory, ala:

+

Prebuilt binary versions of these system images, suitable for running +under the emulator qemu, are uploaded to +the website +each release if you'd like to try before building from source.

+ +

You can cross compile simple three package (toybox+libc+linux) systems +configured to boot to a shell prompt under qemu by setting CROSS_COMPILE= to a +cross compiler prefix (or by installing cross compilers +in the "ccc" subdirectory and specifying a target type with CROSS=) +and also pointing the build at a Linux kernel source directory, ala:

make root CROSS=sh4 LINUX=~/linux

@@ -778,14 +788,19 @@ compiler has a libc built into it, 3) you tell it where to find a Linux kernel source directory with LINUX= on the command line. If you don't say LINUX=, it skips that part of the build and just produces a root filesystem directory -ala the first example in this FAQ answer.

+(root/$CROSS/fs or root/host/fs if no $CROSS target specified), which you +can chroot into if your architecture can run those binaries. (For PID other +than 1, the /init script at the top of the directory sets up and cleans up +the /proc mount points, so chroot root/i686/fs /init is a reasonable +"poke around and look at things" smoketest.)

The CROSS= shortcut expects a "ccc" symlink in the toybox source directory -pointing at a directory full of cross compilers. The ones I test this with are built from the musl-libc -maintainer's +pointing at a directory full of cross compilers. The ones I test this with are +built from the musl-libc maintainer's musl-cross-make -project, built by running toybox's scripts/mcm-buildall.sh in that directory, -and then symlink the resulting "ccc" subdirectory into toybox where CROSS= +project, built by running toybox's +scripts/mcm-buildall.sh in a musl-cross-make checkout directory, +and then symlinking the resulting "ccc" subdirectory into toybox where CROSS= can find them:

@@ -797,8 +812,8 @@
 ln -s $(realpath ccc) ../toybox/ccc
 
-

If you don't want to do that, you can download prebuilt binary versions from Zach van Rijn's site and -just extract them into a "ccc" subdirectory under the toybox source.

+

If you don't want to do that, you can download prebuilt binary versions +and extract them into a "ccc" subdirectory under the toybox source.

Once you've installed the cross compilers, "make root CROSS=help" should list all the available cross compilers it recognizes under ccc, diff -Nru toybox-0.8.8+dfsg/www/git/index.html toybox-0.8.9+dfsg/www/git/index.html --- toybox-0.8.8+dfsg/www/git/index.html 2022-08-12 07:58:03.000000000 +0000 +++ toybox-0.8.9+dfsg/www/git/index.html 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -Not browseable: git clone https://landley.net/toybox/git diff -Nru toybox-0.8.8+dfsg/www/index.html toybox-0.8.9+dfsg/www/index.html --- toybox-0.8.8+dfsg/www/index.html 2022-08-12 07:58:03.000000000 +0000 +++ toybox-0.8.9+dfsg/www/index.html 2023-01-10 19:24:45.000000000 +0000 @@ -8,6 +8,223 @@

News

+

January 8, 2023

+
+

"Why," Arthur said, "is there a sofa in that field?"
+"I told you!" shouted Ford, leaping to his feet. "Eddies in the space-time +continuum!"
+"And this is his sofa, is it?"

+

- The Hitchhiker's Guide to the Galaxy

+

+ +

Toybox 0.8.9 +(git commit) +is out, with prebuilt static binaries and +mkroot images +bootable under QEMU (built using a lightly patched linux-6.1).

+ +

The new nbd-server command interoperates with nbd-client to serve +network block devices (using the v1 protocol), and Moritz Weber contributed a read-only git +implementation to pending.

+ +

Features: New grep fast path for fixed or simple patterns that +don't need the full regex engine (I.E. "^", ".", "$" but not "*" or "[]") +most noticeable when searching for many patterns at once. +Improved tar --xform support parsing flags= and trailing s/// scope +flags (but using --xform now requires toybox sed in the $PATH).

+ +

Added sort -C, lsusb -i, netcat -n, +swapoff -a -v, httpd -v, nbd-client -b, +a uname -p stub to mollify package builds, +sleep accepts multiple arguments, +sed now parses interlaced -e and -f arguments in order, +several new options in readlink and realpath (and better +handling of relative paths), and UDP mode in netcat is more useful now: +netcat -u -s 127.0.0.1 -p 9876 -l can type at netcat -u 127.0.0.1 9876 +in both directions. (Figuring out when the other side hangs up is an unsolved +problem, but that's UDP for you.) +Elliott added ls -N.

+ +

Bugfixes: Fixed off by one error in sort -c output, +fixed sed -z and other sed cases where Linux has never obeyed +Posix (N at EOF does a default print, l escapes \n), +patch --dry-run should no longer create or delete files, +gzip/zcat couldn't handle concatenated archives, +the ./configure of gmake 4.3 depends on test treating one argument -a as a synonym for -e, +autodetectiong compression types in tar should be more reliable now +and sanitize weird path corner cases (like .. past /) better, +scripts/make.sh (and thus "make toybox") should no longer truncate log +files stderr is redirected to, +httpd now handles ? and # in URLs, +xxd disables columns for -c 0 and groups for -g 0 (so you can get +a long interrupted string of hex digits out of it), two fixes to +mountpoint (the conversion to same_file() was inappropriate because +the logic isn't quite the same, and -q should also quiet "not found" errors), +fixed httpd not always displaying index.html files instead of directory +contents, fixed comm - not recognizing it as stdin, and multiple fixes to +timeout which now kills process group and thus child processes, +isn't suspended by SIGTTIN, and recursive commands it calls don't +inherit an inappropriate SIGCHLD handler.

+ +

Yi-Yo Chiang fixed loopback mount (the recent switch to xrunread() +kept the newline from losetup's output) and added tail +123 +(old-style synonym for tail -n +123). +Daniel Mentz added scmversion to the modinfo tag list (Android uses +it for external modules). Alexander Holler fixed su to not require +/etc/shadow when run as root, and nomas2000 reported an inverted test in +date's check for trailing rubbish. Daniel Mentz fixed an off by +one in grep -f that discarded the last character of the pattern file's +contents. Kelvin Zhang made modinfo better at handling symlinks. +Antoni Villalonga fixed fmt on 64 bit big endian systems (like s390x and +powerpc). Li Cheng fixed mount's type detection not autodetecting +the need to bind mount files when type "none" was specified in fstab. +Tomasz Sterna reported that modprobe resolving dependencies shouldn't +feed a NULL pointer to the syscall's options arguments (the kernel goes -EWTF), +and Vincent Donnefort made modprobe work when /proc/modules isn't available.

+ +

Library: +The TOYFLAG_ARGFAIL() exit value is now the default error_msg() sets, +sendfile() now falls back to the read/write loop for any error (not just +EINVAL, the kernel sends ENOSYS and EXDEV and who knows what else), +xgetrandom() can now return arbitrary amounts of data (looping internally +as necessary), +xwaitpid() returns status 127 for cases (like bad PID) which don't +return a status for the PID, +several fixes to xabspath() as part of the readlink/realpath work, +new octal_deslash() to remove octal escapes common in kernel strings +(ala /proc/mounts), +the FLAG_x macros always use a 64 bit type now (so you don't have to +worry about toys.optflags &= ~FLAG_x; blanking the top 32 bits), +replaced memcmp() with new library function smemcmp() to placate ASAN +when returning first difference in known differing arguments of unequal length. +Elliott added three more filesystem types to fs_type_name() which is +used by stat -f and friends.

+ +

Mkroot: +New scripts/test_mkroot.sh runs each target under qemu to confirm +1) it boots, 2) the block device works, 3) networking works, and 4) the clock is set +reasonably. It runs them in parallel, with an httpd instance on the host to +fetch a file from, and a timeout to detect hangs. +New scripts/root/overlay package copies $OVERLAY directory into +target filesystem, ala scripts/mkroot.sh overlay OVERLAY=$PWD/blah +The init script now mounts any /dev/?da on /mnt and when /mnt/init exists +sets $HANDOFF to that instead of running a shell prompt, so you can provide +automated control images that run code in the emulator automatically. (The +emulator process still when whatever $HANDOFF called exits.)

+ +

Some kernel changes finally made it upstream so we can drop workarounds +for them, such as kernel commit +f8f0d06438e5 +fixing the longstanding +"allnoconfig has =y" issue so miniconfig doesn't need to add a magic #comment +line forcing a symbol OFF anymore. The remaining kernel patches (including +several maintained locally but hadn't merged into mkroot) moved to a +seperate repository +so mkroot.sh doesn't have to call sed on the build snapshot. (They're are also +included in the mkroot binary release directory, and were all submitted to lkml +months if not years ago.)

+ +

The host airlock setup (creating the temporary restricted environment +mkroot builds packages in both so they don't pick up strange dependencies from +the host, and to prove the build can use the toybox commands) +removed a bunch of commands (dd, diff, vi, xzcat, ar, nm) no longer +needed by the linux-6.1 build. The kernel patches also remove the need +for gcc (it can use the "cc" symlink all modern distros install, and +autodetects whether that points to gcc or clang) and bc (which is just +generally obsolete). +Left those two in for one more release, but NEXT time building a vanilla +kernel without the gcc removal patch +and bc removal patch +may require you to add HOST_EXTRA="gcc bc" to the mkroot command line +for the airlock build to add extra symlinks to root/build/airlock.

+ +

Added a kernel build for powerpc64 big endian, +m68k and powerpc now support "run-qemu.sh -hda file.img", and m68k only +has one /dev/?db (disabled Apple Desktop Bus in the config). +Added basic module support (the kernel build saves modules in modules.cpio.gz +so it can extract them when you rebuild the root filesystem but not the kernel) +and added scripts/root/tests which (among other things) builds two +innocuous modules that depend on each other (fscache and cachefiles) for +modprobe/insmod testing that hopefully won't break anything else. (If +there was a scripts/root/pending/ tests would be in it: it does not actually +run the test suite under mkroot yet, because toysh needs work.)

+ +

Pending: Elliott made strace build on 32-bit x86. +James Farrell added diff -f. +Alexander Holler fixed two parameter substitution bugs in toysh. +Rob fixed assignment suppression, && and || parsing, math +priority, a number of ASAN test failures, untangled and commented the +brace expansion logic, and other ongoing shell work.

+ +

A large rewrite of diff happened but couldn't be checked in unfinished +because if people are using stuff out of pending, how do you do a pending for +pending? (Meta-pending?)

+ +

Cleanup: +cksum, od, file, getopt, gpiod, +nbd-client, better output messages for host, +and make base64 use line buffering. +Moved -Wno-string-plus-int into portability.sh and had it only apply to +clang. Moved num_cache() out of lib into netstat (its only user).

+ +

In the test suite plumbing, $SKIPNEXT was replaced by $SKIP holding +a count that counts down with each test (so you can easily skip a block of +tests). Fixed txpect to actually listen to error code (oops) and to not +complain about multiple X. The old dochroot() and mkchroot() shell functions were removed from +scripts/runtest.sh because they required root access to use (which mkroot +does not). More conversion to use testcmd in individual command.tests +(both to remove workarounds for shell builtins, and to be more concise). +Remove redundant cleanup from several tests (which run as proper child processes +now, and the work directory is deleted and recreated between each, so +individual tests need less cleanup).

+ +

Portability: More tests now pass on MacOS and FreeBSD +(weird filesystems, root vs wheel group name, dealing with headers that +define stdin as a macro and don't define PATH_MAX, the ancient bash in MacOS +doesn't understand &>>, won't let normal users use "chmod +s", +symlinks don't have all permission bits set, no O_PATH and the Linux definition +is used for something else, and so on), at least half of which Elliott did. +Not sure we've quite got Mac and BSD +doing "gmake test_singlecommand" right yet, but it's a lot closer. +Added all the commands to make bsd_defconfig that compile on FreeBSD 13 +(which is no guarantee they WORK). +ASAN=1 should work in more places now, including make_test_command. +The build uses $CC insted of cc in more places, and doesn't apply $ASAN +for $HOSTCC (the legacy kconfig/ directory stands no chance).

+ +Fedora inexplicably sticks \x09 style hex escapes into the dmesg output and +expects them to be parsed. +Android is still running the test suite under mksh which doesn't +understand bashisms like <(command). LLVM insisted that "ptr - 1" could +never be NULL (demonstrably untrue) and needed some (unsigned long) typecasts +to force it to actually do the math. +Khem Raj aliased timer_settime to timer_settime64 when the first isn't +available. Antoni Villalonga debianized some install paths and added +a bunch of --longopt synonyms for existing options that package builds +use.

+ +

Documentation: The help page +generation logic (toybox help -av) now filter out invisible aliases and +outputs "See command" for visible aliases, plus fixes for broken corner +cases like nohup --help. +The local git repo +now has a simple index.html generated by a +small shell script, +linking to the git format-patch files for +each commit, applicable via git am.

+ +

There was a +bug report that specifying the cross compiler prefix twice +includes the prefix twice (ala CROSS_COMPILE=prefix- CC=prefix-cc results in +prefix-prefix-cc) which is what happens when you ask it to do that, yes. It's now +better documented. (See also +this kernel patch.)

+ +

Yi-Yo Chiang's losetup fix spawned an email thread where the +android developers explained why Android doesn't (and can't) +use devtmpfs. Rob forwarded a link to linux weekly news to see if he could get +any attention from the kernel guys, but they didn't reply.

+

August 12, 2022

Huge as office blocks, silent as birds. They hung in the air exactly the same diff -Nru toybox-0.8.8+dfsg/www/news.html toybox-0.8.9+dfsg/www/news.html --- toybox-0.8.8+dfsg/www/news.html 2022-08-12 07:58:03.000000000 +0000 +++ toybox-0.8.9+dfsg/www/news.html 2023-01-10 19:24:45.000000000 +0000 @@ -8,6 +8,223 @@

News

+

January 8, 2023

+
+

"Why," Arthur said, "is there a sofa in that field?"
+"I told you!" shouted Ford, leaping to his feet. "Eddies in the space-time +continuum!"
+"And this is his sofa, is it?"

+

- The Hitchhiker's Guide to the Galaxy

+

+ +

Toybox 0.8.9 +(git commit) +is out, with prebuilt static binaries and +mkroot images +bootable under QEMU (built using a lightly patched linux-6.1).

+ +

The new nbd-server command interoperates with nbd-client to serve +network block devices (using the v1 protocol), and Moritz Weber contributed a read-only git +implementation to pending.

+ +

Features: New grep fast path for fixed or simple patterns that +don't need the full regex engine (I.E. "^", ".", "$" but not "*" or "[]") +most noticeable when searching for many patterns at once. +Improved tar --xform support parsing flags= and trailing s/// scope +flags (but using --xform now requires toybox sed in the $PATH).

+ +

Added sort -C, lsusb -i, netcat -n, +swapoff -a -v, httpd -v, nbd-client -b, +a uname -p stub to mollify package builds, +sleep accepts multiple arguments, +sed now parses interlaced -e and -f arguments in order, +several new options in readlink and realpath (and better +handling of relative paths), and UDP mode in netcat is more useful now: +netcat -u -s 127.0.0.1 -p 9876 -l can type at netcat -u 127.0.0.1 9876 +in both directions. (Figuring out when the other side hangs up is an unsolved +problem, but that's UDP for you.) +Elliott added ls -N.

+ +

Bugfixes: Fixed off by one error in sort -c output, +fixed sed -z and other sed cases where Linux has never obeyed +Posix (N at EOF does a default print, l escapes \n), +patch --dry-run should no longer create or delete files, +gzip/zcat couldn't handle concatenated archives, +the ./configure of gmake 4.3 depends on test treating one argument -a as a synonym for -e, +autodetectiong compression types in tar should be more reliable now +and sanitize weird path corner cases (like .. past /) better, +scripts/make.sh (and thus "make toybox") should no longer truncate log +files stderr is redirected to, +httpd now handles ? and # in URLs, +xxd disables columns for -c 0 and groups for -g 0 (so you can get +a long interrupted string of hex digits out of it), two fixes to +mountpoint (the conversion to same_file() was inappropriate because +the logic isn't quite the same, and -q should also quiet "not found" errors), +fixed httpd not always displaying index.html files instead of directory +contents, fixed comm - not recognizing it as stdin, and multiple fixes to +timeout which now kills process group and thus child processes, +isn't suspended by SIGTTIN, and recursive commands it calls don't +inherit an inappropriate SIGCHLD handler.

+ +

Yi-Yo Chiang fixed loopback mount (the recent switch to xrunread() +kept the newline from losetup's output) and added tail +123 +(old-style synonym for tail -n +123). +Daniel Mentz added scmversion to the modinfo tag list (Android uses +it for external modules). Alexander Holler fixed su to not require +/etc/shadow when run as root, and nomas2000 reported an inverted test in +date's check for trailing rubbish. Daniel Mentz fixed an off by +one in grep -f that discarded the last character of the pattern file's +contents. Kelvin Zhang made modinfo better at handling symlinks. +Antoni Villalonga fixed fmt on 64 bit big endian systems (like s390x and +powerpc). Li Cheng fixed mount's type detection not autodetecting +the need to bind mount files when type "none" was specified in fstab. +Tomasz Sterna reported that modprobe resolving dependencies shouldn't +feed a NULL pointer to the syscall's options arguments (the kernel goes -EWTF), +and Vincent Donnefort made modprobe work when /proc/modules isn't available.

+ +

Library: +The TOYFLAG_ARGFAIL() exit value is now the default error_msg() sets, +sendfile() now falls back to the read/write loop for any error (not just +EINVAL, the kernel sends ENOSYS and EXDEV and who knows what else), +xgetrandom() can now return arbitrary amounts of data (looping internally +as necessary), +xwaitpid() returns status 127 for cases (like bad PID) which don't +return a status for the PID, +several fixes to xabspath() as part of the readlink/realpath work, +new octal_deslash() to remove octal escapes common in kernel strings +(ala /proc/mounts), +the FLAG_x macros always use a 64 bit type now (so you don't have to +worry about toys.optflags &= ~FLAG_x; blanking the top 32 bits), +replaced memcmp() with new library function smemcmp() to placate ASAN +when returning first difference in known differing arguments of unequal length. +Elliott added three more filesystem types to fs_type_name() which is +used by stat -f and friends.

+ +

Mkroot: +New scripts/test_mkroot.sh runs each target under qemu to confirm +1) it boots, 2) the block device works, 3) networking works, and 4) the clock is set +reasonably. It runs them in parallel, with an httpd instance on the host to +fetch a file from, and a timeout to detect hangs. +New scripts/root/overlay package copies $OVERLAY directory into +target filesystem, ala scripts/mkroot.sh overlay OVERLAY=$PWD/blah +The init script now mounts any /dev/?da on /mnt and when /mnt/init exists +sets $HANDOFF to that instead of running a shell prompt, so you can provide +automated control images that run code in the emulator automatically. (The +emulator process still when whatever $HANDOFF called exits.)

+ +

Some kernel changes finally made it upstream so we can drop workarounds +for them, such as kernel commit +f8f0d06438e5 +fixing the longstanding +"allnoconfig has =y" issue so miniconfig doesn't need to add a magic #comment +line forcing a symbol OFF anymore. The remaining kernel patches (including +several maintained locally but hadn't merged into mkroot) moved to a +seperate repository +so mkroot.sh doesn't have to call sed on the build snapshot. (They're are also +included in the mkroot binary release directory, and were all submitted to lkml +months if not years ago.)

+ +

The host airlock setup (creating the temporary restricted environment +mkroot builds packages in both so they don't pick up strange dependencies from +the host, and to prove the build can use the toybox commands) +removed a bunch of commands (dd, diff, vi, xzcat, ar, nm) no longer +needed by the linux-6.1 build. The kernel patches also remove the need +for gcc (it can use the "cc" symlink all modern distros install, and +autodetects whether that points to gcc or clang) and bc (which is just +generally obsolete). +Left those two in for one more release, but NEXT time building a vanilla +kernel without the gcc removal patch +and bc removal patch +may require you to add HOST_EXTRA="gcc bc" to the mkroot command line +for the airlock build to add extra symlinks to root/build/airlock.

+ +

Added a kernel build for powerpc64 big endian, +m68k and powerpc now support "run-qemu.sh -hda file.img", and m68k only +has one /dev/?db (disabled Apple Desktop Bus in the config). +Added basic module support (the kernel build saves modules in modules.cpio.gz +so it can extract them when you rebuild the root filesystem but not the kernel) +and added scripts/root/tests which (among other things) builds two +innocuous modules that depend on each other (fscache and cachefiles) for +modprobe/insmod testing that hopefully won't break anything else. (If +there was a scripts/root/pending/ tests would be in it: it does not actually +run the test suite under mkroot yet, because toysh needs work.)

+ +

Pending: Elliott made strace build on 32-bit x86. +James Farrell added diff -f. +Alexander Holler fixed two parameter substitution bugs in toysh. +Rob fixed assignment suppression, && and || parsing, math +priority, a number of ASAN test failures, untangled and commented the +brace expansion logic, and other ongoing shell work.

+ +

A large rewrite of diff happened but couldn't be checked in unfinished +because if people are using stuff out of pending, how do you do a pending for +pending? (Meta-pending?)

+ +

Cleanup: +cksum, od, file, getopt, gpiod, +nbd-client, better output messages for host, +and make base64 use line buffering. +Moved -Wno-string-plus-int into portability.sh and had it only apply to +clang. Moved num_cache() out of lib into netstat (its only user).

+ +

In the test suite plumbing, $SKIPNEXT was replaced by $SKIP holding +a count that counts down with each test (so you can easily skip a block of +tests). Fixed txpect to actually listen to error code (oops) and to not +complain about multiple X. The old dochroot() and mkchroot() shell functions were removed from +scripts/runtest.sh because they required root access to use (which mkroot +does not). More conversion to use testcmd in individual command.tests +(both to remove workarounds for shell builtins, and to be more concise). +Remove redundant cleanup from several tests (which run as proper child processes +now, and the work directory is deleted and recreated between each, so +individual tests need less cleanup).

+ +

Portability: More tests now pass on MacOS and FreeBSD +(weird filesystems, root vs wheel group name, dealing with headers that +define stdin as a macro and don't define PATH_MAX, the ancient bash in MacOS +doesn't understand &>>, won't let normal users use "chmod +s", +symlinks don't have all permission bits set, no O_PATH and the Linux definition +is used for something else, and so on), at least half of which Elliott did. +Not sure we've quite got Mac and BSD +doing "gmake test_singlecommand" right yet, but it's a lot closer. +Added all the commands to make bsd_defconfig that compile on FreeBSD 13 +(which is no guarantee they WORK). +ASAN=1 should work in more places now, including make_test_command. +The build uses $CC insted of cc in more places, and doesn't apply $ASAN +for $HOSTCC (the legacy kconfig/ directory stands no chance).

+ +Fedora inexplicably sticks \x09 style hex escapes into the dmesg output and +expects them to be parsed. +Android is still running the test suite under mksh which doesn't +understand bashisms like <(command). LLVM insisted that "ptr - 1" could +never be NULL (demonstrably untrue) and needed some (unsigned long) typecasts +to force it to actually do the math. +Khem Raj aliased timer_settime to timer_settime64 when the first isn't +available. Antoni Villalonga debianized some install paths and added +a bunch of --longopt synonyms for existing options that package builds +use.

+ +

Documentation: The help page +generation logic (toybox help -av) now filter out invisible aliases and +outputs "See command" for visible aliases, plus fixes for broken corner +cases like nohup --help. +The local git repo +now has a simple index.html generated by a +small shell script, +linking to the git format-patch files for +each commit, applicable via git am.

+ +

There was a +bug report that specifying the cross compiler prefix twice +includes the prefix twice (ala CROSS_COMPILE=prefix- CC=prefix-cc results in +prefix-prefix-cc) which is what happens when you ask it to do that, yes. It's now +better documented. (See also +this kernel patch.)

+ +

Yi-Yo Chiang's losetup fix spawned an email thread where the +android developers explained why Android doesn't (and can't) +use devtmpfs. Rob forwarded a link to linux weekly news to see if he could get +any attention from the kernel guys, but they didn't reply.

+

August 12, 2022

Huge as office blocks, silent as birds. They hung in the air exactly the same diff -Nru toybox-0.8.8+dfsg/www/roadmap.html toybox-0.8.9+dfsg/www/roadmap.html --- toybox-0.8.8+dfsg/www/roadmap.html 2022-08-12 07:58:03.000000000 +0000 +++ toybox-0.8.9+dfsg/www/roadmap.html 2023-01-10 19:24:45.000000000 +0000 @@ -131,8 +131,8 @@ qalter qdel qhold qmove qmsg qrerun qrls qselect qsig qstat qsub).

Some commands are for a compiler toolchain (ar c99 cflow ctags cxref gencat -iconv lex m4 make nm strings strip tsort yacc) which is outside of toybox's -mandate and should be supplied externally. (Some of these may be +iconv lex m4 make nm strings strip tsort yacc) which is out of scope for +toybox and should be supplied externally. (Some of these might be revisited later, but not for toybox 1.0.)

Some commands are part of a command shell, and can't be implemented as @@ -269,24 +269,25 @@

The RFCs are more about protocols than commands. The noise level is extremely high: there's thousands of RFCs, many describing a proposed idea that never took off, and less than 1% of the resulting documents are -currently relevant to toybox. And the documents are numbered based on the +currently relevant to toybox. The documents are numbered based on the order they were received, with no real attempt at coherently indexing the result. As with man pages they can be long and complicated or terse and impenetrable, have developed a certain amount of bureaucracy over the years, and often the easiest way to understand what -they document is to find an earlier version to read first.

+they document is to find an earlier version to read first. +(The greybeard community problem where all documentation is written by people +who don't remember NOT already knowing this stuff.)

That said, RFC documents can be useful (especially for networking protocols) and the four URL templates the recommended starting files -for new commands (toys/example/skeleton.c or toys/example/hello.c depending on how much -plumbing you want to start with) provide point to posix, lsb, man, and -rfc pages.

+for new commands (toys/example/{skeleton,hello}.c) provide point to posix, lsb, +man, and rfc pages.


Use case: provide a self-hosting development environment

-

The following commands were enough to build the Aboriginal Linux development +

Once upon a time, the following commands were enough to build the Aboriginal Linux development environment, boot it to a shell prompt, and build Linux From Scratch 6.8 under it.

@@ -318,12 +319,12 @@ To replace that toysh needs to supply several bash extensions _and_ work when called under the name "bash".

-

The above command list was collected using a command line recording wrapper, -see scripts/record-commands and toys/example/logpath.c, which -scripts/mkroot.sh uses to populate root/log/*-commands.txt. Try +

The above command list was collected using a command line recording wrapper +(scripts/record-commands and toys/example/logpath.c) which scripts/mkroot.sh +also uses to populate root/log/*-commands.txt. Try awk '{print $1}' root/build/log/*-commands.txt | sort -u | grep -v musl | xargs -after building a mkroot target to get a similar command list used by that -build.

+after building a mkroot target to see the list of commands called out +of the $PATH during that build.

Stages and moving targets

@@ -1250,6 +1251,7 @@ pwgen readelf unicode rsync linux32 hd strace +gpiodetect gpiofind gpioget gpioinfo gpioset httpd uclampset
@@ -1287,7 +1289,6 @@
  • less: less (not: lessecho lesskey)
  • gzip: zcat [gzip] [gunzip] [zcmp] [zdiff] [zegrep] [zfgrep] [zgrep] [zless] [zmore] (not: gzexe uncompress zforce znew)
  • -
  • make: [make]
  • patch: patch
  • tar: tar
  • procps-ng: free pgrep pidof pkill ps sysctl top uptime vmstat w watch