stdbuf - Run COMMAND, with modified buffering operations for its standard streams.
$ stdbuf -i0 -oL -e4567 ls / Would use unbuffered input, line-buffered output and 4567 bytes buffer for stderr. Twiddling stdio buffers is done by LD_PRELOAD'ing a library -- libstdbuf.so Our implementation can be used interchangably with the coreutils version and vice versa. The coreutils libstdbuf.so with glibc weights in with: text data bss dec hex filename 2622 624 8 3254 cb6 /usr/lib/x86_64-linux-gnu/coreutils/libstdbuf.so in debian, fyi. Our version: 1313 448 0 1761 6e1 coreutils/libstdbuf.so.1.32.0.git function old new delta stdbuf_main - 212 +212 .rodata 183904 184096 +192 packed_usage 33147 33248 +101 stdbuf_longopts - 26 +26 applet_main 3160 3168 +8 applet_names 2721 2728 +7 ------------------------------------------------------------------------------ (add/remove: 3/0 grow/shrink: 4/0 up/down: 546/0) Total: 546 bytes text data bss dec hex filename 939202 4240 1992 945434 e6d1a busybox_old 939618 4240 1992 945850 e6eba busybox_unstripped for glibc $ size coreutils/stdbuf.o coreutils/libstdbuf.o coreutils/libstdbuf.so text data bss dec hex filename 313 0 0 313 139 coreutils/stdbuf.o 497 8 0 505 1f9 coreutils/libstdbuf.o 1563 480 0 2043 7fb coreutils/libstdbuf.so for uClibc libstdbuf.o is around 400b Signed-off-by: Bernhard Reutner-Fischer <rep.dot....@gmail.com> --- .gitignore | 1 + Makefile.custom | 26 +++++++++ Makefile.flags | 2 + coreutils/libstdbuf.c | 125 ++++++++++++++++++++++++++++++++++++++++++ coreutils/stdbuf.c | 115 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 269 insertions(+) create mode 100644 coreutils/libstdbuf.c create mode 100644 coreutils/stdbuf.c diff --git a/.gitignore b/.gitignore index becd9bf6d..47d4e7c64 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,7 @@ Config.in /busybox /busybox_old /busybox_unstripped* +coreutils/libstdbuf.so* # # Backups / patches diff --git a/Makefile.custom b/Makefile.custom index 6f679c4e1..7599ee467 100644 --- a/Makefile.custom +++ b/Makefile.custom @@ -35,6 +35,28 @@ ifeq ($(CONFIG_FEATURE_INDIVIDUAL),y) INSTALL_OPTS:= --binaries LIBBUSYBOX_SONAME:= 0_lib/libbusybox.so.$(BB_VER) endif +# directory containing libbusybox.so or stdbuf LD_PRELOADed libstdbuf.so +# FIXME: applets/install.sh uses plain CC but should use CC CFLAGS ! +#BUSYBOX_LIBDIR ?= $(dir $(abspath $(shell $(CC) $(CFLAGS) -print-file-name=libc.so))) + +# start-of-install.sh-workarounds +# FIXME: applets/install.sh should follow usage_compressed, I.e.: +# applets/usage_compressed:test "$SED" || SED=sed +SED ?= sed +# FIXME: FORNOW: do what applets/install.sh wrongly does: +BUSYBOX_LIBDIR:= $(shell $(CC) -print-file-name=libc.so | $(SED) -n 's%^.*\(/lib[^\/]*\)/libc.so%\1%p') +ifeq ($(strip $(BUSYBOX_LIBDIR)),) +BUSYBOX_LIBDIR:= /lib +endif +BUSYBOX_LIBDIR:= $(BUSYBOX_LIBDIR)/ +#end-of-install.sh-workarounds +LIBSTDBUF_SONAME:= +ifeq ($(CONFIG_LIBSTDBUF),y) +LIBSTDBUF_SONAME:= libstdbuf.so +DO_INSTALL_LIBS += coreutils/$(LIBSTDBUF_SONAME).$(BB_VER) coreutils/$(LIBSTDBUF_SONAME) +endif +export BUSYBOX_LIBDIR LIBSTDBUF_SONAME + install: $(srctree)/applets/install.sh busybox busybox.links $(Q)DO_INSTALL_LIBS="$(strip $(LIBBUSYBOX_SONAME) $(DO_INSTALL_LIBS))" \ $(SHELL) $< $(CONFIG_PREFIX) $(INSTALL_OPTS) @@ -48,6 +70,10 @@ ifeq ($(strip $(CONFIG_FEATURE_SUID)),y) @echo -------------------------------------------------- @echo endif +ifeq ($(CONFIG_LIBSTDBUF),y) + @# Undo install.sh chmod 0644 "$prefix/$libdir/`basename $i`" || exit 1 + @chmod 0755 $(CONFIG_PREFIX)/$(BUSYBOX_LIBDIR)$(LIBSTDBUF_SONAME) +endif install-noclobber: INSTALL_OPTS+=--noclobber install-noclobber: install diff --git a/Makefile.flags b/Makefile.flags index 667481983..d943427de 100644 --- a/Makefile.flags +++ b/Makefile.flags @@ -108,6 +108,8 @@ ARCH_FPIC ?= -fpic ARCH_FPIE ?= -fpie ARCH_PIE ?= -pie +export ARCH_FPIC ARCH_FPIE ARCH_PIE + # Usage: $(eval $(call pkg_check_modules,VARIABLE-PREFIX,MODULES)) define pkg_check_modules $(1)_CFLAGS := $(shell $(PKG_CONFIG) $(PKG_CONFIG_FLAGS) --cflags $(2)) diff --git a/coreutils/libstdbuf.c b/coreutils/libstdbuf.c new file mode 100644 index 000000000..ba1bc92dc --- /dev/null +++ b/coreutils/libstdbuf.c @@ -0,0 +1,125 @@ +/* vi: set sw=4 ts=4: */ +/* + * stdbuf - Run COMMAND, with modified buffering operations for its + * standard streams. libstdbuf.so LD_PRELOAD helper. + * Copyright (c) 2017-2020 Bernhard Reutner-Fischer + * + * Licensed under GPLv2 or later, see file LICENSE in this source tree. + */ +//config:config LIBSTDBUF +//config: bool "libstdbuf (0.4 / 1.2 kb)" +//config: default STDBUF +//config: help +//config: Build libstdbuf.so LD_PRELOAD helper library +//config: If you disable this option the system libstdbuf.so is used +//config: for the stdlib(1) applet. +//config: Note that busybox' libstdbuf.so is compatible with the +//config: corresponding library from coreutils, so can be used +//config: interchangeably (and vice versa). +//kbuild:extra-$(CONFIG_LIBSTDBUF) += libstdbuf.so +//kbuild:# Add libstdbuf.o so it's .libstdbuf.o.cmd is read. +//kbuild:# Otherwise we'd not see cmd_ and wrongly think the rule changed: +//kbuild:extra-$(CONFIG_LIBSTDBUF) += libstdbuf.o +//kbuild:clean-files += libstdbuf.so libstdbuf.so.* +//kbuild:CFLAGS_libstdbuf.o:=$(ARCH_FPIC) +//kbuild:LDFLAGS_libstdbuf.so.$(BB_VER):=$(ARCH_FPIC) -shared -Wl,-soname=$(notdir $(LIBSTDBUF_SONAME)) -Wl,--warn-common -Wl,--warn-shared-textrel -Wl,-Map,$(@).map # -Wl,-z,now -Wl,--enable-new-dtags +//kbuild:coreutils/libstdbuf.so.$(BB_VER): coreutils/libstdbuf.o +//kbuild: $(call if_changed,ld) +//kbuild:coreutils/libstdbuf.so: coreutils/libstdbuf.so.$(BB_VER) +//kbuild: @echo ' LINK $@' # FIXME {quiet,}cmd_ln +//kbuild: $(Q)ln -sf $(<F) $@ + +#include "platform.h" +#include <sys/types.h> +#include <errno.h> +#include <stdlib.h> +#include <stdio.h> + +#if !defined likely && defined __GNUC__ && __GNUC__ >= 3 +# define likely(x) __builtin_expect((!!(x)),1) +#else +# define likely(x) (x) +#endif +#if !defined unlikely && defined __GNUC__ && __GNUC__ >= 3 +# define unlikely(x) __builtin_expect((!!(x)),0) +#else +# define unlikely(x) (x) +#endif + +static void do_setvbuf(FILE *stream, const char* const file, const char *mode) +{ + int md; + size_t sz = 0; + char *buf = NULL; + const char errors[] ALIGN1 = "invalid\0could not set" +#if defined __GLIBC__ && !defined(__UCLIBC__) + "\0OOM allocating" +#endif + ; + unsigned char x = -1; +#if 00 && defined(__GNUC__) && __GNUC__ > 8 +# if 1 + const char files[] ALIGN1 = "stderr\0stdout\0stdin"; + const char* const file = files + (2-(fileno(stream))) * 7; +# else + const char files[] ALIGN1 = "stdin\0\0stdout\0stderr"; + const char* const file = files + 7 * fileno(stream); +# endif +#else +# if 00 + const char* file; + switch (fileno(stream)) { + case 0: file = "stdin"; break; + case 1: file = "stdout"; break; + default: file = "stderr"; break; + } +# endif +#endif + if (mode == NULL) + return; + else if (*mode == '0') + md = _IONBF; + else if (*mode == 'L') + md = _IOLBF; + else { + char *invalid; + md = _IOFBF; + /* Note: We could call into libbusybox.so if available. */ + errno = 0; +#if defined SIZE_MAX && defined ULLONG_MAX && SIZE_MAX == ULLONG_MAX + sz = strtoull(mode, &invalid, 10); +#else + sz = strtoul(mode, &invalid, 10); +#endif + /* Note: if (sz > 0) might be sufficient, but be paranoid */ + if (errno == 0 && *invalid == '\0' && sz > 0) { + /* BUG: glibc seem to ignore size if buf==NULL */ + /* uClibc behaves properly, i guess musl does too */ +#if defined __GLIBC__ && !defined(__UCLIBC__) + buf = malloc (sz); + if (buf == NULL) { + x = 22; // OOM allocating + } +#endif + } else { + x = 0; // invalid + } + } + if (likely(x == (unsigned char)-1)) { + if (likely(setvbuf(stream, buf, md, sz) == 0)) + return; + x = 8; // could not set + } + fprintf(stderr, "%s buffering of %s to mode %s\n", errors + x, file, mode); +#if defined __GLIBC__ && !defined(__UCLIBC__) + /* IF_FEATURE_CLEAN_UP() */ free(buf); +#endif +} + +static void INIT_FUNC +busybox_adjust_stdbuf(void) +{ + do_setvbuf(stderr, "stderr", getenv("_STDBUF_E")); + do_setvbuf(stdin, "stdin", getenv("_STDBUF_I")); + do_setvbuf(stdout, "stdout", getenv("_STDBUF_O")); +} diff --git a/coreutils/stdbuf.c b/coreutils/stdbuf.c new file mode 100644 index 000000000..c5f134b21 --- /dev/null +++ b/coreutils/stdbuf.c @@ -0,0 +1,115 @@ +/* vi: set sw=4 ts=4: */ +/* + * stdbuf - Run COMMAND, with modified buffering operations for its + * standard streams + * Copyright (c) 2017-2020 Bernhard Reutner-Fischer + * + * Licensed under GPLv2 or later, see file LICENSE in this source tree. + */ +//config:config STDBUF +//config: bool "stdbuf (0.3 kb)" +//config: default y +//config: help +//config: Run COMMAND, with modified buffering operations for its +//config: standard streams. +//config: Note: Your libc has to support LD_PRELOAD for this to work. +//config:config FEATURE_STDBUF_LONG_OPTIONS +//config: bool "Enable long options" +//config: default y +//config: depends on STDBUF && LONG_OPTS +//applet:IF_STDBUF(APPLET_NOEXEC(stdbuf, stdbuf, BB_DIR_USR_BIN, BB_SUID_DROP, stdbuf)) +//kbuild:lib-$(CONFIG_STDBUF) += stdbuf.o +//kbuild:CFLAGS_stdbuf.o:=-DLIBSTDBUF_SONAME=$(squote)$(quote)$(BUSYBOX_LIBDIR)$(notdir $(LIBSTDBUF_SONAME))$(quote)$(squote) +//usage:#define stdbuf_trivial_usage +//usage: "OPTION... COMMAND" +//usage:#define stdbuf_full_usage "\n\n" +//usage: "Run COMMAND, with modified buffering operations for its standard streams\n" +//usage: "\n -i" +//usage: IF_FEATURE_STDBUF_LONG_OPTIONS(",--input=MODE") +//usage: " adjust standard input stream buffering" +//usage: "\n -o" +//usage: IF_FEATURE_STDBUF_LONG_OPTIONS(",--output=MODE") +//usage: " adjust standard output stream buffering" +//usage: "\n -e" +//usage: IF_FEATURE_STDBUF_LONG_OPTIONS(",--error=MODE") +//usage: " adjust standard error stream buffering" +//usage: "\n\nMODE:" +//usage: "\n\tL: line buffer (not for input)" +//usage: "\n\t0: unbuffered" +//usage: "\n\tNUMBER: of buffer bytes" +//usage:#define stdbuf_example_usage +//usage: "$ stdbuf -i0 -oL tail -f /x.log\n" + +#include "libbb.h" +//#include "common_bufsiz.h" + +/* LIBSTDBUF_SONAME is supposedly something like: + * $(dirname $(readlink -f $(cc $CFLAGS -print-file-name=libc.so)))/libstdbuf.so + */ +#ifndef LIBSTDBUF_SONAME +/* Make sure this matches applets/install.sh fallback libdir ! */ +# define LIBSTDBUF_SONAME "/lib/libstdbuf.so" +#endif + +#if !defined likely && defined __GNUC__ && __GNUC__ >= 3 +# define likely(x) __builtin_expect((!!(x)),1) +#else +# define likely(x) (x) +#endif +#if !defined unlikely && defined __GNUC__ && __GNUC__ >= 3 +# define unlikely(x) __builtin_expect((!!(x)),0) +#else +# define unlikely(x) (x) +#endif + +#if ENABLE_FEATURE_STDBUF_LONG_OPTIONS +#define getopt32 getopt32long +static const char stdbuf_longopts[] ALIGN1 = + "input\0" Required_argument "i" + "output\0" Required_argument "o" + "error\0" Required_argument "e" +; +#endif + +int stdbuf_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int stdbuf_main(int argc UNUSED_PARAM, char **argv) +{ + const char * const LD_PRELOAD = "LD_PRELOAD"; + char *old_env, *new_env = (char*) LIBSTDBUF_SONAME; + char STDBUF[] ALIGN1 = "_STDBUF_I"; + unsigned i = 0; + unsigned char opt; + char *streams[3]; /* input, output, error */ + + opt = getopt32(argv, IF_FEATURE_STDBUF_LONG_OPTIONS("^") + "+" /* stop argument processing on first non-option arg */ + "i:o:e:" "\0" + "-1" /* at least one non-option arg is required */ + "i:o:e:" /* at least one of these args is required */ + IF_FEATURE_STDBUF_LONG_OPTIONS(, stdbuf_longopts) + , &streams[0], &streams[1], &streams[2]); + for (; i < 3; i++) { + const char *value = "L"; + if (! (opt & (1 << i))) /* skip OPT_i, OPT_o, OPT_e if not set */ + continue; + if (i != 0) + STDBUF[sizeof("_STDBUF_I") - 2] = i == 1 ? 'O' : 'E'; + /* Reject "L" for input */ + if (i == 0 || *(streams[i]) != 'L') { + /* Note that the library takes just numbers, without suffix. */ + value = xasprintf("%llu", xatoull_sfx(streams[i], kmg_i_suffixes)); + } + xsetenv(STDBUF, value); + /* Not worth freeing value */ +// if (ENABLE_FEATURE_CLEAN_UP && *value != 'L') +// free(value); + } + old_env = getenv(LD_PRELOAD); + if (unlikely(old_env != NULL)) + new_env = xasprintf("%s:%s", new_env, old_env); + xsetenv(LD_PRELOAD, new_env); + /* Not worth freeing new_env */ +// if (ENABLE_FEATURE_CLEAN_UP && old_env) +// free(new_env); + BB_EXECVP_or_die(argv + optind); +} -- 2.35.2 _______________________________________________ busybox mailing list busybox@busybox.net http://lists.busybox.net/mailman/listinfo/busybox