[RFC,PATCH 1/1] new applet: stdbuf(1)

Bernhard Reutner-Fischer rep.dot.nop at gmail.com
Wed Apr 20 14:54:58 UTC 2022


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.nop at 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



More information about the busybox mailing list