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

Reply via email to