diff options
author | Pádraig Brady <P@draigBrady.com> | 2008-12-17 11:30:03 +0000 |
---|---|---|
committer | Pádraig Brady <P@draigBrady.com> | 2009-06-17 14:54:29 +0100 |
commit | a5a2a406f8d65f0e852d9ed7fbfb630c6b81dd7f (patch) | |
tree | d6ec40e289bbe748eb05f5e6cb9e8d376d79f446 /src/libstdbuf.c | |
parent | ff6fe3d17d9294f2494b4535a320bc9bfc86cc49 (diff) | |
download | coreutils-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.
Diffstat (limited to 'src/libstdbuf.c')
-rw-r--r-- | src/libstdbuf.c | 141 |
1 files changed, 141 insertions, 0 deletions
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); +} |