1
0
mirror of git://git.sv.gnu.org/coreutils.git synced 2026-02-10 09:21:58 +02:00

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.
This commit is contained in:
Pádraig Brady
2008-12-17 11:30:03 +00:00
parent ff6fe3d17d
commit a5a2a406f8
20 changed files with 772 additions and 9 deletions

3
.x-sc_system_h_headers Normal file
View File

@@ -0,0 +1,3 @@
^src/libstdbuf\.c$
^src/system\.h$
^src/copy\.h$

View File

@@ -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

View 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

5
NEWS
View File

@@ -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

6
README
View File

@@ -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.

3
cfg.mk
View File

@@ -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

View 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])

View File

@@ -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

1
man/.gitignore vendored
View File

@@ -72,6 +72,7 @@ sleep.1
sort.1
split.1
stat.1
stdbuf.1
stty.1
su.1
sum.1

View 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

16
man/stdbuf.x Normal file
View File

@@ -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.

View 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

2
src/.gitignore vendored
View 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

View 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; \

141
src/libstdbuf.c Normal file
View File

@@ -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);
}

386
src/stdbuf.c Normal file
View File

@@ -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:
*/

View 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 \

View File

@@ -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

View File

@@ -32,6 +32,7 @@ my %exit_status =
expr => 0,
nohup => 127,
sort => 2,
stdbuf => 125,
test => 0,
timeout => 125,
true => 0,

86
tests/misc/stdbuf Executable file
View File

@@ -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