summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPádraig Brady <P@draigBrady.com>2008-12-17 11:30:03 +0000
committerPádraig Brady <P@draigBrady.com>2009-06-17 14:54:29 +0100
commita5a2a406f8d65f0e852d9ed7fbfb630c6b81dd7f (patch)
treed6ec40e289bbe748eb05f5e6cb9e8d376d79f446
parentff6fe3d17d9294f2494b4535a320bc9bfc86cc49 (diff)
downloadcoreutils-a5a2a406f8d65f0e852d9ed7fbfb630c6b81dd7f.tar.xz
stdbuf: A new program to run a command with modified stdio buffering
* 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.
-rw-r--r--.x-sc_system_h_headers3
-rw-r--r--AUTHORS1
-rw-r--r--Makefile.am1
-rw-r--r--NEWS5
-rw-r--r--README6
-rw-r--r--cfg.mk3
-rw-r--r--configure.ac15
-rw-r--r--doc/coreutils.texi84
-rw-r--r--man/.gitignore1
-rw-r--r--man/Makefile.am1
-rw-r--r--man/stdbuf.x16
-rw-r--r--po/POTFILES.in2
-rw-r--r--src/.gitignore2
-rw-r--r--src/Makefile.am24
-rw-r--r--src/libstdbuf.c141
-rw-r--r--src/stdbuf.c386
-rw-r--r--tests/Makefile.am1
-rwxr-xr-xtests/misc/help-version2
-rwxr-xr-xtests/misc/invalid-opt1
-rwxr-xr-xtests/misc/stdbuf86
20 files changed, 772 insertions, 9 deletions
diff --git a/.x-sc_system_h_headers b/.x-sc_system_h_headers
new file mode 100644
index 000000000..14e020f34
--- /dev/null
+++ b/.x-sc_system_h_headers
@@ -0,0 +1,3 @@
+^src/libstdbuf\.c$
+^src/system\.h$
+^src/copy\.h$
diff --git a/AUTHORS b/AUTHORS
index fa3c02963..7095db09a 100644
--- 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
diff --git a/Makefile.am b/Makefile.am
index 97be46a11..99fc937f8 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -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 754f9e2eb..a769cf6f9 100644
--- 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 08e0babaf..7545eabe9 100644
--- 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 d3ec9de02..34123d5a7 100644
--- 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
diff --git a/configure.ac b/configure.ac
index 4eb640e65..32d295815 100644
--- a/configure.ac
+++ b/configure.ac
@@ -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])
diff --git a/doc/coreutils.texi b/doc/coreutils.texi
index 180629520..91b3f5781 100644
--- a/doc/coreutils.texi
+++ b/doc/coreutils.texi
@@ -105,6 +105,7 @@
* 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
diff --git a/man/.gitignore b/man/.gitignore
index e9e270da9..1085ff055 100644
--- a/man/.gitignore
+++ b/man/.gitignore
@@ -72,6 +72,7 @@ sleep.1
sort.1
split.1
stat.1
+stdbuf.1
stty.1
su.1
sum.1
diff --git a/man/Makefile.am b/man/Makefile.am
index ee16a3ff2..cacaba663 100644
--- a/man/Makefile.am
+++ b/man/Makefile.am
@@ -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
index 000000000..93f0b8e4b
--- /dev/null
+++ b/man/stdbuf.x
@@ -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.
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 6c291ccb6..6ded56861 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -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
diff --git a/src/.gitignore b/src/.gitignore
index bc1452390..f2886dea2 100644
--- a/src/.gitignore
+++ b/src/.gitignore
@@ -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
diff --git a/src/Makefile.am b/src/Makefile.am
index 3bed7b1af..4f21c8633 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -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
index 000000000..8eec0960a
--- /dev/null
+++ b/src/libstdbuf.c
@@ -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
index 000000000..89f2242de
--- /dev/null
+++ b/src/stdbuf.c
@@ -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:
+ */
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 24f54baef..59737a05d 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -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 \
diff --git a/tests/misc/help-version b/tests/misc/help-version
index ee7460058..983148b87 100755
--- a/tests/misc/help-version
+++ b/tests/misc/help-version
@@ -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
diff --git a/tests/misc/invalid-opt b/tests/misc/invalid-opt
index cbd41cae1..9af2dd24c 100755
--- a/tests/misc/invalid-opt
+++ b/tests/misc/invalid-opt
@@ -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
index 000000000..6f79e771f
--- /dev/null
+++ b/tests/misc/stdbuf
@@ -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