]> git.ipfire.org Git - thirdparty/coreutils.git/commitdiff
stdbuf: A new program to run a command with modified stdio buffering
authorPádraig Brady <P@draigBrady.com>
Wed, 17 Dec 2008 11:30:03 +0000 (11:30 +0000)
committerPádraig Brady <P@draigBrady.com>
Wed, 17 Jun 2009 13:54:29 +0000 (14:54 +0100)
* AUTHORS: Register as the author.
* NEWS: Mention this change.
* README: Add stdbuf command to list.
* configure.ac: Only enable on ELF systems with GCC.
* cfg.mk (sc_system_h_headers): Use VC_LIST_EXCEPT rather than
VC_LIST, so we can add an exception, if needed.
* .x-sc_system_h_headers: New file.  Exempt libstdbuf.c.
* Makefile.am (syntax_check_exceptions): Add .x-sc_system_h_headers.
* doc/coreutils.texi (stdbuf invocation): Add stdbuf info.
* man/.gitignore: Ignore generated manpage.
* src/.gitignore: Ignore stdbuf and libstdbuf.so binaries.
* man/Makefile.am (stdbuf.1): Add dependency.
* man/stdbuf.x: New file with example usage.
* po/POTFILES.in: Reference new command and shared library sources.
* src/Makefile.am (build_if_possible__progs): Add stdbuf and libstdbuf,
(pkglib_PROGRAMS): Reference optional shared lib,
(libstdbuf_so_LDADD): Ensure we don't link with non PIC libcoreutils.a.
(libstdbuf_so_LDFLAGS): Add -shared GCC option,
(libstdbuf_so_CFLAGS): Add -fPIC GCC option.
(check-README): Exclude libstbuf.
(check-AUTHORS): ditto.
(sc_tight_scope): Exclude functions starting with __.
* src/libstdbuf.c: The LD_PRELOAD shared library to control buffering.
* src/stdbuf.c: New file to setup env variables before execing command.
* tests/Makefile.am: Reference new test file.
* tests/misc/help-version: Set expected exit codes.
* tests/misc/invalid-opt: ditto.
* tests/misc/stdbuf: Add 9 tests.

20 files changed:
.x-sc_system_h_headers [new file with mode: 0644]
AUTHORS
Makefile.am
NEWS
README
cfg.mk
configure.ac
doc/coreutils.texi
man/.gitignore
man/Makefile.am
man/stdbuf.x [new file with mode: 0644]
po/POTFILES.in
src/.gitignore
src/Makefile.am
src/libstdbuf.c [new file with mode: 0644]
src/stdbuf.c [new file with mode: 0644]
tests/Makefile.am
tests/misc/help-version
tests/misc/invalid-opt
tests/misc/stdbuf [new file with mode: 0755]

diff --git a/.x-sc_system_h_headers b/.x-sc_system_h_headers
new file mode 100644 (file)
index 0000000..14e020f
--- /dev/null
@@ -0,0 +1,3 @@
+^src/libstdbuf\.c$
+^src/system\.h$
+^src/copy\.h$
diff --git a/AUTHORS b/AUTHORS
index fa3c02963ca0e33f390fb173735f08a50dfe62a5..7095db09a5d9514c1042ac3cbb5ae94a8cc943aa 100644 (file)
--- a/AUTHORS
+++ b/AUTHORS
@@ -76,6 +76,7 @@ sleep: Jim Meyering, Paul Eggert
 sort: Mike Haertel, Paul Eggert
 split: Torbjörn Granlund, Richard M. Stallman
 stat: Michael Meskes
+stdbuf: Pádraig Brady
 stty: David MacKenzie
 su: David MacKenzie
 sum: Kayvan Aghaiepour, David MacKenzie
index 97be46a119f903ac946c9b05c3260f8bf3548e4f..99fc937f8f439d65fd815a0935ab31ee5f6da4ba 100644 (file)
@@ -52,6 +52,7 @@ syntax_check_exceptions =             \
   .x-sc_require_config_h_first         \
   .x-sc_space_tab                      \
   .x-sc_sun_os_names                   \
+  .x-sc_system_h_headers               \
   .x-sc_trailing_blank                 \
   .x-sc_unmarked_diagnostics           \
   .x-sc_useless_cpp_parens
diff --git a/NEWS b/NEWS
index 754f9e2eba6401decd75beb03eaf39dd7ab6c59f..a769cf6f98a1832cc19abea06b7cfc0f914ebb8a 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -12,6 +12,11 @@ GNU coreutils NEWS                                    -*- outline -*-
   part of the line after the start position was used as the sort key.
   [This bug appears to have been present in "the beginning".]
 
+** New programs
+
+  stdbuf: A new program to run a command with modified stdio buffering
+  for its standard streams.
+
 ** Changes in behavior
 
   ls --color: files with multiple hard links are no longer colored differently
diff --git a/README b/README
index 08e0babaf7a56213d8403b6f591ecd2d9af3ecea..7545eabe99bdd9580ae70977c2ef67a0a5d2efee 100644 (file)
--- a/README
+++ b/README
@@ -13,9 +13,9 @@ The programs that can be built with this package are:
   link ln logname ls md5sum mkdir mkfifo mknod mktemp mv nice nl nohup
   od paste pathchk pinky pr printenv printf ptx pwd readlink rm rmdir
   runcon seq sha1sum sha224sum sha256sum sha384sum sha512sum shred shuf
-  sleep sort split stat stty su sum sync tac tail tee test timeout touch tr
-  true truncate tsort tty uname unexpand uniq unlink uptime users vdir wc who
-  whoami yes
+  sleep sort split stat stdbuf stty su sum sync tac tail tee test timeout
+  touch tr true truncate tsort tty uname unexpand uniq unlink uptime users
+  vdir wc who whoami yes
 
 See the file NEWS for a list of major changes in the current release.
 
diff --git a/cfg.mk b/cfg.mk
index d3ec9de023a9c81644b87a0f04e112592d5c1314..34123d5a7a8a4fc73fbb44a249b17fb4bb785281 100644 (file)
--- a/cfg.mk
+++ b/cfg.mk
@@ -160,8 +160,7 @@ sc_system_h_headers: .re-list
        @if test -f $(srcdir)/src/system.h; then                        \
          trap 'rc=$$?; rm -f .re-list; exit $$rc' 0 1 2 3 15;          \
          grep -nE -f .re-list                                          \
-             $$($(VC_LIST) src |                                       \
-                grep -Ev '((copy|system)\.h|parse-gram\.c)$$')         \
+             $$($(VC_LIST_EXCEPT) | grep '^src/')                      \
            && { echo '$(ME): the above are already included via system.h'\
                  1>&2;  exit 1; } || :;                                \
        fi
index 4eb640e65b65b9c82163f3d79d61a5df3cc5603c..32d29581547d5fc69f3022f47c77e5f8d2c43f35 100644 (file)
@@ -321,6 +321,19 @@ if test $gl_cv_list_mounted_fs = yes && test $gl_cv_fs_space = yes; then
   gl_ADD_PROG([optional_bin_progs], [df])
 fi
 
+# Limit stdbuf to ELF systems with GCC
+optional_pkglib_progs=
+AC_MSG_CHECKING([whether this is an ELF system])
+AC_EGREP_CPP([yes], [#if __ELF__
+yes
+#endif], [elf_sys=yes], [elf_sys=no])
+AC_MSG_RESULT([$elf_sys])
+if test "$elf_sys" = "yes" && \
+   test "$GCC" = "yes"; then
+  gl_ADD_PROG([optional_bin_progs], [stdbuf])
+  gl_ADD_PROG([optional_pkglib_progs], [libstdbuf.so])
+fi
+
 ############################################################################
 mk="$srcdir/src/Makefile.am"
 # Extract all literal names from the definition of $(EXTRA_PROGRAMS)
@@ -393,6 +406,8 @@ MAN=`echo "$MAN"|sed 's/\@<:@\.1//'`
 
 OPTIONAL_BIN_PROGS=`echo "$optional_bin_progs "|sed 's/ /\$(EXEEXT) /g;s/ $//'`
 AC_SUBST([OPTIONAL_BIN_PROGS])
+OPTIONAL_PKGLIB_PROGS=`echo "$optional_pkglib_progs " | sed 's/ $//'`
+AC_SUBST([OPTIONAL_PKGLIB_PROGS])
 NO_INSTALL_PROGS_DEFAULT=$no_install_progs_default
 AC_SUBST([NO_INSTALL_PROGS_DEFAULT])
 
index 180629520cdbd64c2ae6766b4f8f8639616fb2b1..91b3f578140088b9e3f56f3e7f76fb6aa6ffbd6b 100644 (file)
 * sort: (coreutils)sort invocation.             Sort text files.
 * split: (coreutils)split invocation.           Split into fixed-size pieces.
 * stat: (coreutils)stat invocation.             Report file(system) status.
+* stdbuf: (coreutils)stdbuf invocation.         Modify stdio buffering.
 * stty: (coreutils)stty invocation.             Print/change terminal settings.
 * su: (coreutils)su invocation.                 Modify user and group ID.
 * sum: (coreutils)sum invocation.               Print traditional checksum.
@@ -197,7 +198,7 @@ Free Documentation License''.
 * User information::                   id logname whoami groups users who
 * System context::                     date uname hostname hostid uptime
 * SELinux context::                    chcon runcon
-* Modified command invocation::        chroot env nice nohup su timeout
+* Modified command invocation::        chroot env nice nohup stdbuf su timeout
 * Process control::                    kill
 * Delaying::                           sleep
 * Numeric operations::                 factor seq
@@ -434,6 +435,7 @@ Modified command invocation
 * env invocation::               Run a command in a modified environment
 * nice invocation::              Run a command with modified niceness
 * nohup invocation::             Run a command immune to hangups
+* stdbuf invocation::            Run a command with modified I/O buffering
 * su invocation::                Run a command with substitute user and group ID
 * timeout invocation::           Run a command with a time limit
 
@@ -14160,6 +14162,7 @@ user, etc.
 * env invocation::              Modify environment variables.
 * nice invocation::             Modify niceness.
 * nohup invocation::            Immunize to hangups.
+* stdbuf invocation::           Modify buffering of standard streams.
 * su invocation::               Modify user and group ID.
 * timeout invocation::          Run with time limit.
 @end menu
@@ -14523,6 +14526,85 @@ the exit status of @var{command} otherwise
 @end display
 
 
+@node stdbuf invocation
+@section @command{stdbuf}: Run a command with modified I/O stream buffering
+
+@pindex stdbuf
+@cindex standard streams, buffering
+@cindex line buffered
+
+@command{stdbuf} allows one modify the buffering operations of the
+three standard I/O streams associated with a program.  Synopsis:
+
+@example
+stdbuf @var{option}@dots{} @var{command}
+@end example
+
+Any additional @var{arg}s are passed as additional arguments to the
+@var{command}.
+
+The program accepts the following options.  Also see @ref{Common options}.
+
+@table @samp
+
+@item -i @var{mode}
+@itemx --input=@var{mode}
+@opindex -i
+@opindex --input
+Adjust the standard input stream buffering.
+
+@item -o @var{mode}
+@itemx --output=@var{mode}
+@opindex -o
+@opindex --output
+Adjust the standard output stream buffering.
+
+@item -e @var{mode}
+@itemx --error=@var{mode}
+@opindex -e
+@opindex --error
+Adjust the standard error stream buffering.
+
+@end table
+
+The @var{mode} can be specified as follows:
+
+@table @samp
+
+@item L
+Set the stream to line buffered mode.
+In this mode data is coalesced until a newline is output or
+input is read from any stream attached to a terminal device.
+This option is invalid with standard input.
+
+@item 0
+Disable buffering of the selected stream.
+In this mode data is output immediately and only the
+amount of data requested is read from input.
+
+@item @var{size}
+Specify the size of the buffer to use in fully buffered mode.
+@multiplierSuffixesNoBlocks{size}
+
+@end table
+
+NOTE: If @var{command} adjusts the buffering of its standard streams
+(@command{tee} does for e.g.) then that will override corresponding settings
+changed by @command{stdbuf}.  Also some filters (like @command{dd} and
+@command{cat} etc.) don't use streams for I/O, and are thus unaffected
+by @command{stdbuf} settings.
+
+@cindex exit status of @command{stdbuf}
+Exit status:
+
+@display
+125 if @command{stdbuf} itself fails
+126 if @var{command} is found but cannot be invoked
+127 if @var{command} cannot be found
+the exit status of @var{command} otherwise
+@end display
+
+
 @node su invocation
 @section @command{su}: Run a command with substitute user and group ID
 
index e9e270da949486722c4206f3978d29bcc38b3d0b..1085ff055fc5dd7a24ca7a2f443abe01d06bf6dc 100644 (file)
@@ -72,6 +72,7 @@ sleep.1
 sort.1
 split.1
 stat.1
+stdbuf.1
 stty.1
 su.1
 sum.1
index ee16a3ff2eb5d2ded8007bef67e27cdfb34f0b15..cacaba66342fc9f3713be47675f148c7fcdb1312 100644 (file)
@@ -105,6 +105,7 @@ sleep.1:    $(common_dep)   $(srcdir)/sleep.x       ../src/sleep.c
 sort.1:                $(common_dep)   $(srcdir)/sort.x        ../src/sort.c
 split.1:       $(common_dep)   $(srcdir)/split.x       ../src/split.c
 stat.1:                $(common_dep)   $(srcdir)/stat.x        ../src/stat.c
+stdbuf.1:      $(common_dep)   $(srcdir)/stdbuf.x      ../src/stdbuf.c
 stty.1:                $(common_dep)   $(srcdir)/stty.x        ../src/stty.c
 su.1:          $(common_dep)   $(srcdir)/su.x          ../src/su.c
 sum.1:         $(common_dep)   $(srcdir)/sum.x         ../src/sum.c
diff --git a/man/stdbuf.x b/man/stdbuf.x
new file mode 100644 (file)
index 0000000..93f0b8e
--- /dev/null
@@ -0,0 +1,16 @@
+'\" Copyright (C) 2009 Free Software Foundation, Inc.
+'\"
+'\" This is free software.  You may redistribute copies of it under the terms
+'\" of the GNU General Public License <http://www.gnu.org/licenses/gpl.html>.
+'\" There is NO WARRANTY, to the extent permitted by law.
+[NAME]
+stdbuf \- Run COMMAND, with modified buffering operations for its standard streams.
+[DESCRIPTION]
+.\" Add any additional description here
+[EXAMPLES]
+.B tail -f access.log | stdbuf -oL cut -d \(aq \(aq -f1 | uniq
+.br
+This will immedidately display unique entries from access.log
+[BUGS]
+On GLIBC platforms, specifying a buffer size, i.e. using fully buffered mode
+will result in undefined operation.
index 6c291ccb6168c36df73e2091e6010199d1975add..6ded568619065f67d6fd0a906e9084d3e5e1468b 100644 (file)
@@ -73,6 +73,7 @@ src/id.c
 src/install.c
 src/join.c
 src/kill.c
+src/libstdbuf.c
 src/link.c
 src/ln.c
 src/logname.c
@@ -109,6 +110,7 @@ src/sleep.c
 src/sort.c
 src/split.c
 src/stat.c
+src/stdbuf.c
 src/stty.c
 src/su.c
 src/sum.c
index bc1452390d0a28a629a6f4e571536125d2542b97..f2886dea24c6a78d4c9c371e8db50ebf191871f4 100644 (file)
@@ -40,6 +40,7 @@ hostname
 id
 join
 kill
+libstdbuf.so
 libver.a
 link
 ln
@@ -81,6 +82,7 @@ sleep
 sort
 split
 stat
+stdbuf
 stty
 su
 sum
index 3bed7b1af6f8432def1e68404da7d729db5ed66c..4f21c863315c0d244b0c08d89de5e3558d04810c 100644 (file)
@@ -24,7 +24,7 @@ no_install__progs = \
   arch hostname su
 
 build_if_possible__progs = \
-  chroot df hostid nice pinky stty su uname uptime users who
+  chroot df hostid nice pinky stdbuf libstdbuf.so stty su uname uptime users who
 
 AM_CFLAGS = $(WARN_CFLAGS) $(WERROR_CFLAGS)
 
@@ -48,6 +48,8 @@ bin_PROGRAMS = $(OPTIONAL_BIN_PROGS)
 
 noinst_PROGRAMS = setuidgid getlimits
 
+pkglib_PROGRAMS = $(OPTIONAL_PKGLIB_PROGS)
+
 noinst_HEADERS = \
   chown-core.h \
   copy.h \
@@ -91,6 +93,7 @@ du_LDADD = $(LDADD)
 getlimits_LDADD = $(LDADD)
 ptx_LDADD = $(LDADD)
 split_LDADD = $(LDADD)
+stdbuf_LDADD = $(LDADD)
 timeout_LDADD = $(LDADD)
 truncate_LDADD = $(LDADD)
 
@@ -170,6 +173,7 @@ du_LDADD += $(LIBICONV)
 getlimits_LDADD += $(LIBICONV)
 ptx_LDADD += $(LIBICONV)
 split_LDADD += $(LIBICONV)
+stdbuf_LDADD += $(LIBICONV)
 timeout_LDADD += $(LIBICONV)
 truncate_LDADD += $(LIBICONV)
 
@@ -286,6 +290,16 @@ sha512sum_CPPFLAGS = -DHASH_ALGO_SHA512=1 $(AM_CPPFLAGS)
 
 ginstall_CPPFLAGS = -DENABLE_MATCHPATHCON=1 $(AM_CPPFLAGS)
 
+# Ensure we don't link against libcoreutils.a as that lib is
+# not compiled with -fPIC which causes issues on 64 bit at least
+libstdbuf_so_LDADD =
+
+# Note libstdbuf is only compiled if GCC is available
+# (as per the check in configure.ac), so these flags should be available.
+# libtool is probably required to relax this dependency.
+libstdbuf_so_LDFLAGS = -shared
+libstdbuf_so_CFLAGS = -fPIC $(AM_CFLAGS)
+
 editpl = sed -e 's,@''PERL''@,$(PERL),g'
 
 BUILT_SOURCES += dircolors.h
@@ -369,6 +383,7 @@ check-README:
        rm -rf $(pr) $(pm)
        echo $(all_programs) \
         | tr -s ' ' '\n' | sed -e 's,$(EXEEXT)$$,,;s/ginstall/install/' \
+        | sed /libstdbuf/d \
         | $(ASSORT) -u > $(pm) && \
        sed -n '/^The programs .* are:/,/^[a-zA-Z]/p' $(top_srcdir)/README \
          | sed -n '/^   */s///p' | tr -s ' ' '\n' > $(pr)
@@ -394,6 +409,7 @@ check-AUTHORS: $(all_programs)
          && { echo "$@: skipping this check"; exit 0; }; \
        rm -f $(au_actual) $(au_dotdot);                \
        for i in `ls $(all_programs) | sed -e 's,$(EXEEXT)$$,,' \
+           | sed /libstdbuf/d                          \
            | $(ASSORT) -u`; do                         \
          test "$$i" = '[' && continue;                 \
          exe=$$i;                                      \
@@ -416,7 +432,9 @@ check-AUTHORS: $(all_programs)
 # Most functions in src/*.c should have static scope.
 # Any that don't must be marked with `extern', but `main'
 # and `usage' are exceptions.  They're always extern, but
-# don't need to be marked.
+# don't need to be marked. Also functions starting with __
+# are exempted due to possibly being added by the compiler
+# (when compiled as a shared library for example).
 #
 # The second nm|grep checks for file-scope variables with `extern' scope.
 .PHONY: sc_tight_scope
@@ -427,7 +445,7 @@ sc_tight_scope: $(bin_PROGRAMS)
               test -f $$f && d= || d=$(srcdir)/; echo $$d$$f; done`;   \
        hdr=`for f in $(noinst_HEADERS); do                             \
               test -f $$f && d= || d=$(srcdir)/; echo $$d$$f; done`;   \
-       ( printf 'main\nusage\n';                                       \
+       ( printf 'main\nusage\n_.*\n';                                  \
          grep -h -A1 '^extern .*[^;]$$' $$src                          \
            | grep -vE '^(extern |--)' | sed 's/ .*//';                 \
          perl -ne '/^extern \S+ (\S*) \(/ and print "$$1\n"' $$hdr;    \
diff --git a/src/libstdbuf.c b/src/libstdbuf.c
new file mode 100644 (file)
index 0000000..8eec096
--- /dev/null
@@ -0,0 +1,141 @@
+/* libstdbuf -- a shared lib to preload to setup stdio buffering for a command
+   Copyright (C) 2009 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+/* Written by Pádraig Brady.  LD_PRELOAD idea from Brian Dessent.  */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "system.h"
+#include "verify.h"
+
+/* Note currently for glibc (2.3.5) the following call does not change the
+   the buffer size, and more problematically does not give any indication
+   that the new size request was ignored:
+
+       setvbuf (stdout, (char*)NULL, _IOFBF, 8192);
+
+   The ISO C99 standard section 7.19.5.6 on the setvbuf function says:
+
+   ... If buf is not a null pointer, the array it points to _may_ be used
+   instead of a buffer allocated by the setvbuf function and the argument
+   size specifies the size of the array; otherwise, size _may_ determine
+   the size of a buffer allocated by the setvbuf function. ...
+
+   Obviously some interpret the above to mean setvbuf(....,size)
+   is only a hint from the application which I don't agree with.
+
+   FreeBSD's libc seems more sensible in this regard. From the man page:
+
+   The size argument may be given as zero to obtain deferred optimal-size
+   buffer allocation as usual.  If it is not zero, then except for
+   unbuffered files, the buf argument should point to a buffer at least size
+   bytes long; this buffer will be used instead of the current buffer.  (If
+   the size argument is not zero but buf is NULL, a buffer of the given size
+   will be allocated immediately, and released on close.  This is an extension
+   to ANSI C; portable code should use a size of 0 with any NULL buffer.)
+   --------------------
+   Another issue is that on glibc-2.7 the following doesn't buffer
+   the first write if it's greater than 1 byte.
+
+       setvbuf(stdout,buf,_IOFBF,127);
+
+   Now the POSIX standard says that "allocating a buffer of size bytes does
+   not necessarily imply that all of size bytes are used for the buffer area".
+   However I think it's just a buggy implementation due to the various
+   inconsistencies with write sizes and subsequent writes.  */
+
+static const char *
+fileno_to_name (const int fd)
+{
+  const char *ret = NULL;
+
+  switch (fd)
+    {
+    case 0:
+      ret = "stdin";
+      break;
+    case 1:
+      ret = "stdout";
+      break;
+    case 2:
+      ret = "stderr";
+      break;
+    default:
+      ret = "unknown";
+      break;
+    }
+
+  return ret;
+}
+
+static void
+apply_mode (FILE *stream, const char *mode)
+{
+  char *buf = NULL;
+  int setvbuf_mode;
+  size_t size = 0;
+
+  if (*mode == '0')
+    setvbuf_mode = _IONBF;
+  else if (*mode == 'L')
+    setvbuf_mode = _IOLBF;      /* FIXME: should we allow 1ML  */
+  else
+    {
+      setvbuf_mode = _IOFBF;
+      verify (SIZE_MAX <= ULONG_MAX);
+      size = strtoul (mode, NULL, 10);
+      if (size > 0)
+        {
+          if (!(buf = malloc (size)))   /* will be freed by fclose()  */
+            {
+              /* We could defer the allocation to libc, however since
+                 glibc currently ignores the combination of NULL buffer
+                 with non zero size, we'll fail here.  */
+              fprintf (stderr,
+                       _("failed to allocate a %" PRIuMAX
+                         " byte stdio buffer\n"), (uintmax_t) size);
+              return;
+            }
+        }
+      else
+        {
+          fprintf (stderr, _("invalid buffering mode %s for %s\n"),
+                   mode, fileno_to_name (fileno (stream)));
+          return;
+        }
+    }
+
+  if (setvbuf (stream, buf, setvbuf_mode, size) != 0)
+    {
+      fprintf (stderr, _("could not set buffering of %s to mode %s\n"),
+               fileno_to_name (fileno (stream)), mode);
+    }
+}
+
+__attribute__ ((constructor)) static void
+stdbuf (void)
+{
+  char *e_mode = getenv ("_STDBUF_E");
+  char *i_mode = getenv ("_STDBUF_I");
+  char *o_mode = getenv ("_STDBUF_O");
+  if (e_mode) /* Do first so can write errors to stderr  */
+    apply_mode (stderr, e_mode);
+  if (i_mode)
+    apply_mode (stdin, i_mode);
+  if (o_mode)
+    apply_mode (stdout, o_mode);
+}
diff --git a/src/stdbuf.c b/src/stdbuf.c
new file mode 100644 (file)
index 0000000..89f2242
--- /dev/null
@@ -0,0 +1,386 @@
+/* stdbuf -- setup the standard streams for a command
+   Copyright (C) 2009 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+/* Written by Pádraig Brady.  */
+
+#include <config.h>
+#include <stdio.h>
+#include <getopt.h>
+#include <sys/types.h>
+#include <assert.h>
+
+#include "system.h"
+#include "error.h"
+#include "posixver.h"
+#include "quote.h"
+#include "xstrtol.h"
+#include "c-ctype.h"
+
+/* The official name of this program (e.g., no `g' prefix).  */
+#define PROGRAM_NAME "stdbuf"
+#define LIB_NAME "libstdbuf.so" /* FIXME: don't hardcode  */
+
+#define AUTHORS proper_name_utf8 ("Padraig Brady", "P\303\241draig Brady")
+
+/* Internal error  */
+enum { EXIT_CANCELED = 125 };
+
+static char *program_path;
+
+extern char **environ;
+
+static struct
+{
+  size_t size;
+  int optc;
+  char *optarg;
+} stdbuf[3];
+
+static struct option const longopts[] =
+{
+  {"input", required_argument, NULL, 'i'},
+  {"output", required_argument, NULL, 'o'},
+  {"error", required_argument, NULL, 'e'},
+  {GETOPT_HELP_OPTION_DECL},
+  {GETOPT_VERSION_OPTION_DECL},
+  {NULL, 0, NULL, 0}
+};
+
+/* Set size to the value of STR, interpreted as a decimal integer,
+   optionally multiplied by various values.
+   Return -1 on error, 0 on success.
+
+   This supports dd BLOCK size suffixes.
+   Note we don't support dd's b=512, c=1, w=2 or 21x512MiB formats.  */
+static int
+parse_size (char const *str, size_t *size)
+{
+  uintmax_t tmp_size;
+  enum strtol_error e = xstrtoumax (str, NULL, 10, &tmp_size, "EGkKMPTYZ0");
+  if (e == LONGINT_OK && tmp_size > SIZE_MAX)
+    e = LONGINT_OVERFLOW;
+
+  if (e == LONGINT_OK)
+    {
+      errno = 0;
+      *size = tmp_size;
+      return 0;
+    }
+
+  errno = (e == LONGINT_OVERFLOW ? EOVERFLOW : 0);
+  return -1;
+}
+
+void
+usage (int status)
+{
+  if (status != EXIT_SUCCESS)
+    fprintf (stderr, _("Try `%s --help' for more information.\n"),
+             program_name);
+  else
+    {
+      printf (_("Usage: %s OPTION... COMMAND\n"), program_name);
+      fputs (_("\
+Run COMMAND, with modified buffering operations for its standard streams.\n\
+\n\
+"), stdout);
+      fputs (_("\
+Mandatory arguments to long options are mandatory for short options too.\n\
+"), stdout);
+      fputs (_("\
+  -i, --input=MODE   Adjust standard input stream buffering\n\
+  -o, --output=MODE  Adjust standard output stream buffering\n\
+  -e, --error=MODE   Adjust standard error stream buffering\n\
+"), stdout);
+      fputs (HELP_OPTION_DESCRIPTION, stdout);
+      fputs (VERSION_OPTION_DESCRIPTION, stdout);
+      fputs (_("\n\
+If MODE is `L' then corresponding stream will be line buffered.\n\
+This option is invalid with standard input.\n"), stdout);
+      fputs (_("\n\
+If MODE is `0' then corresponding stream will be unbuffered.\n\
+"), stdout);
+      fputs (_("\n\
+Otherwise MODE is a number which may be followed by one of the following:\n\
+KB 1000, K 1024, MB 1000*1000, M 1024*1024, and so on for G, T, P, E, Z, Y.\n\
+In this case the corresponding stream will be fully buffered with the buffer\n\
+size set to MODE bytes.\n\
+"), stdout);
+      fputs (_("\n\
+NOTE: If COMMAND adjusts the buffering of its standard streams (`tee' does\n\
+for e.g.) then that will override corresponding settings changed by `stdbuf'.\n\
+Also some filters (like `dd' and `cat' etc.) don't use streams for I/O,\n\
+and are thus unaffected by `stdbuf' settings.\n\
+"), stdout);
+      emit_bug_reporting_address ();
+    }
+  exit (status);
+}
+
+/* argv[0] can be anything really, but generally it contains
+   the path to the executable or just a name if it was executed
+   using $PATH. In the latter case to get the path we can:
+   search getenv("PATH"), readlink("/prof/self/exe"), getenv("_"),
+   dladdr(), pstat_getpathname(), etc.  */
+
+static void
+set_program_path (const char *arg)
+{
+  if (strchr (arg, '/'))        /* Use absolute or relative paths directly.  */
+    {
+      program_path = dir_name (arg);
+    }
+  else
+    {
+      char *path;
+      char tmppath[PATH_MAX + 1];
+      ssize_t len = readlink ("/proc/self/exe", tmppath, sizeof (tmppath) - 1);
+      if (len > 0)
+        {
+          tmppath[len] = '\0';
+          program_path = dir_name (tmppath);
+        }
+      else if ((path = getenv ("PATH")))
+        {
+          char *dir;
+          path = xstrdup (path);
+          for (dir = strtok (path, ":"); dir != NULL; dir = strtok (NULL, ":"))
+            {
+              int req = snprintf (tmppath, sizeof (tmppath), "%s/%s", dir, arg);
+              if (req >= sizeof (tmppath))
+                {
+                  error (0, 0, _("path truncated when looking for %s"),
+                         quote (arg));
+                }
+              else if (access (tmppath, X_OK) == 0)
+                {
+                  program_path = dir_name (tmppath);
+                  break;
+                }
+            }
+          free (path);
+        }
+    }
+}
+
+static int
+optc_to_fileno (int c)
+{
+  int ret = -1;
+
+  switch (c)
+    {
+    case 'e':
+      ret = STDERR_FILENO;
+      break;
+    case 'i':
+      ret = STDIN_FILENO;
+      break;
+    case 'o':
+      ret = STDOUT_FILENO;
+      break;
+    }
+
+  return ret;
+}
+
+static void
+set_LD_PRELOAD (void)
+{
+  int ret;
+  char *old_libs = getenv ("LD_PRELOAD");
+  char *LD_PRELOAD;
+
+  /* Note this would auto add the appropriate search path for "libstdbuf.so":
+     gcc stdbuf.c -Wl,-rpath,'$ORIGIN' -Wl,-rpath,$PKGLIBDIR
+     However we want the lookup done for the exec'd command not stdbuf.
+
+     Since we don't link against libstdbuf.so add it to LIBDIR rather than
+     LIBEXECDIR, as we'll search for it in the "sys default" case below.  */
+  char const *const search_path[] = {
+    program_path,
+    PKGLIBDIR,
+    "",                         /* sys default */
+    NULL
+  };
+
+  char const *const *path = search_path;
+  char *libstdbuf;
+
+  do
+    {
+      struct stat sb;
+
+      if (!**path)              /* system default  */
+        {
+          libstdbuf = xstrdup (LIB_NAME);
+          break;
+        }
+      ret = asprintf (&libstdbuf, "%s/%s", *path, LIB_NAME);
+      if (ret < 0)
+        xalloc_die ();
+      if (stat (libstdbuf, &sb) == 0)   /* file_exists  */
+        break;
+      free (libstdbuf);
+    }
+  while (*++path);
+
+  /* FIXME: Do we need to support libstdbuf.dll, c:, '\' separators etc?  */
+
+  if (old_libs)
+    ret = asprintf (&LD_PRELOAD, "LD_PRELOAD=%s:%s", old_libs, libstdbuf);
+  else
+    ret = asprintf (&LD_PRELOAD, "LD_PRELOAD=%s", libstdbuf);
+
+  if (ret < 0)
+    xalloc_die ();
+
+  free (libstdbuf);
+
+  ret = putenv (LD_PRELOAD);
+
+  if (ret != 0)
+    {
+      error (EXIT_CANCELED, errno,
+             _("failed to update the environment with %s"),
+             quote (LD_PRELOAD));
+    }
+}
+
+/* Populate environ with _STDBUF_I=$MODE _STDBUF_O=$MODE _STDBUF_E=$MODE  */
+
+static void
+set_libstdbuf_options (void)
+{
+  int i;
+
+  for (i = 0; i < ARRAY_CARDINALITY (stdbuf); i++)
+    {
+      if (stdbuf[i].optarg)
+        {
+          char *var;
+          int ret;
+
+          if (*stdbuf[i].optarg == 'L')
+            ret = asprintf (&var, "%s%c=L", "_STDBUF_",
+                            toupper (stdbuf[i].optc));
+          else
+            ret = asprintf (&var, "%s%c=%" PRIuMAX, "_STDBUF_",
+                            toupper (stdbuf[i].optc),
+                            (uintmax_t) stdbuf[i].size);
+          if (ret < 0)
+            xalloc_die ();
+
+          if (putenv (var) != 0)
+            {
+              error (EXIT_CANCELED, errno,
+                     _("failed to update the environment with %s"),
+                     quote (var));
+            }
+        }
+    }
+}
+
+int
+main (int argc, char **argv)
+{
+  int c;
+
+  initialize_main (&argc, &argv);
+  set_program_name (argv[0]);
+  setlocale (LC_ALL, "");
+  bindtextdomain (PACKAGE, LOCALEDIR);
+  textdomain (PACKAGE);
+
+  initialize_exit_failure (EXIT_CANCELED);
+  atexit (close_stdout);
+
+  while ((c = getopt_long (argc, argv, "+i:o:e:", longopts, NULL)) != -1)
+    {
+      int opt_fileno;
+
+      switch (c)
+        {
+        /* Old McDonald had a farm ei...  */
+        case 'e':
+        case 'i':
+        case 'o':
+          opt_fileno = optc_to_fileno (c);
+          assert (0 < opt_fileno && opt_fileno <= ARRAY_CARDINALITY (stdbuf));
+          stdbuf[opt_fileno].optc = c;
+          while (c_isspace (*optarg))
+            optarg++;
+          stdbuf[opt_fileno].optarg = optarg;
+          if (c == 'i' && *optarg == 'L')
+            {
+              /* -oL will be by far the most common use of this utility,
+                 but one could easily think -iL might have the same affect,
+                 so disallow it as it could be confusing.  */
+              error (0, 0, _("line buffering stdin is meaningless"));
+              usage (EXIT_CANCELED);
+            }
+
+          if (!STREQ (optarg, "L")
+              && parse_size (optarg, &stdbuf[opt_fileno].size) == -1)
+            error (EXIT_CANCELED, errno, _("invalid mode %s"), quote (optarg));
+
+          break;
+
+        case_GETOPT_HELP_CHAR;
+
+        case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
+
+        default:
+          usage (EXIT_CANCELED);
+        }
+    }
+
+  argv += optind;
+  argc -= optind;
+
+  /* must specify at least 1 command.  */
+  if (argc < 1)
+    {
+      error (0, 0, _("missing operand"));
+      usage (EXIT_CANCELED);
+    }
+
+  /* FIXME: Should we mandate at least one option?  */
+
+  set_libstdbuf_options ();
+
+  /* Try to preload libstdbuf first from the same path as
+     stdbuf is running from.  */
+  set_program_path (argv[0]);
+  if (!program_path)
+    program_path = xstrdup (PKGLIBDIR);  /* Need to init to non NULL.  */
+  set_LD_PRELOAD ();
+  free (program_path);
+
+  execvp (*argv, argv);
+
+  {
+    int exit_status = (errno == ENOENT ? EXIT_ENOENT : EXIT_CANNOT_INVOKE);
+    error (0, errno, _("failed to run command %s"), quote (argv[0]));
+    exit (exit_status);
+  }
+}
+
+/*
+ * Local variables:
+ *  indent-tabs-mode: nil
+ * End:
+ */
index 24f54baef3e7bf283988bf88d5a0ab988e8370dc..59737a05d294495e99be1bf750fd0bb0bd637e42 100644 (file)
@@ -217,6 +217,7 @@ TESTS =                                             \
   misc/split-l                                 \
   misc/stat-fmt                                        \
   misc/stat-printf                             \
+  misc/stdbuf                                  \
   misc/stty                                    \
   misc/stty-invalid                            \
   misc/stty-row-col                            \
index ee7460058333303993591e28ee3a0de5be0dc49e..983148b87b27a15a48cb2b71b16fba680cc04c9e 100755 (executable)
@@ -28,6 +28,7 @@ export SHELL
 . $srcdir/test-lib.sh
 
 expected_failure_status_nohup=127
+expected_failure_status_stdbuf=125
 expected_failure_status_timeout=125
 expected_failure_status_printenv=2
 expected_failure_status_tty=3
@@ -148,6 +149,7 @@ printf_args=foo
 seq_args=10
 sleep_args=0
 su_args=--version
+stdbuf_args="-oL true"
 timeout_args=--version
 
 # I'd rather not run sync, since it spins up disks that I've
index cbd41cae11196a241d85688403d0843f5b2ff4cd..9af2dd24cf72f36ce1efb68d1a3aa5f80ffa8a14 100755 (executable)
@@ -32,6 +32,7 @@ my %exit_status =
     expr => 0,
     nohup => 127,
     sort => 2,
+    stdbuf => 125,
     test => 0,
     timeout => 125,
     true => 0,
diff --git a/tests/misc/stdbuf b/tests/misc/stdbuf
new file mode 100755 (executable)
index 0000000..6f79e77
--- /dev/null
@@ -0,0 +1,86 @@
+#!/bin/sh
+# Exercise stdbuf functionality
+
+# Copyright (C) 2009 Free Software Foundation, Inc.
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+if test "$VERBOSE" = yes; then
+  set -x
+  stdbuf --version
+fi
+
+. $srcdir/test-lib.sh
+getlimits_
+
+# Use a fifo rather than a pipe in the tests below
+# so that the producer (uniq) will wait until the
+# consumer (dd) opens the fifo therefore increasing
+# the chance that dd will read the data from each
+# write separately.
+mkfifo fifo || framework_failure
+
+fail=0
+
+# Verify input parameter checking
+stdbuf -o1 true || fail=1 # verify size syntax
+stdbuf -oK true || fail=1 # verify size syntax
+stdbuf -o0 true || fail=1 # verify unbuffered syntax
+stdbuf -oL true || fail=1 # verify line buffered syntax
+stdbuf -ol true && fail=1 # Capital 'L' required
+stdbuf -o$SIZE_OFLOW true && fail=1 # size too large
+stdbuf -iL true && fail=1 # line buffering stdin disallowed
+
+# Ensure line buffering stdout takes effect
+printf '1\n' > exp
+dd count=1 if=fifo > out 2> err &
+(printf '1\n'; sleep .2; printf '2\n') | stdbuf -oL uniq > fifo
+wait # for dd to complete
+compare out exp || fail=1
+
+# Ensure un buffering stdout takes effect
+printf '1\n' > exp
+dd count=1 if=fifo > out 2> err &
+(printf '1\n'; sleep .2; printf '2\n') | stdbuf -o0 uniq > fifo
+wait # for dd to complete
+compare out exp || fail=1
+
+# Ensure un buffering stdin takes effect
+#  The following works for me, but is racy. I.E. we're depending
+#  on dd to run and close the fifo before the second write by uniq.
+#  If we add a sleep, then we're just testing -oL
+    # printf '3\n' > exp
+    # dd count=1 if=fifo > /dev/null 2> err &
+    # printf '1\n\2\n3\n' | (stdbuf -i0 -oL uniq > fifo; cat) > out
+    # wait # for dd to complete
+    # compare out exp || fail=1
+#  One could remove the need for dd (used to close the fifo to get uniq to quit
+#  early), if head -n1 read stdin char by char. Note uniq | head -c2 doesn't
+#  suffice due to the buffering implicit in the pipe.  sed currently does read
+#  stdin char by char, so we can test with `sed 1q`.  However I'm wary about
+#  adding this dependency on a program outside of coreutils.
+    # printf '2\n' > exp
+    # printf '1\n2\n' | (stdbuf -i0 sed 1q >/dev/null; cat) > out
+    # compare out exp || fail=1
+
+# Ensure block buffering stdout takes effect
+# We don't currently test block buffering failures as
+# this doesn't work on on GLIBC-2.7 or GLIBC-2.9 at least.
+   # printf '1\n2\n' > exp
+   # dd count=1 if=fifo > out 2> err &
+   # (printf '1\n'; sleep .2; printf '2\n') | stdbuf -o4 uniq > fifo
+   # wait # for dd to complete
+   # compare out exp || fail=1
+
+Exit $fail