/* stdbuf -- setup the standard streams for a command Copyright (C) 2009-2011 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 . */ /* Written by Pádraig Brady. */ #include #include #include #include #include #include "system.h" #include "error.h" #include "filenamecat.h" #include "quote.h" #include "xreadlink.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") static char *program_path; 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' the corresponding stream will be line buffered.\n\ This option is invalid with standard input.\n"), stdout); fputs (_("\n\ If MODE is `0' the 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_ancillary_info (); } 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 = xreadlink ("/proc/self/exe"); if (path) program_path = dir_name (path); else if ((path = getenv ("PATH"))) { char *dir; path = xstrdup (path); for (dir = strtok (path, ":"); dir != NULL; dir = strtok (NULL, ":")) { char *candidate = file_name_concat (dir, arg, NULL); if (access (candidate, X_OK) == 0) { program_path = dir_name (candidate); free (candidate); break; } free (candidate); } } 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,$PKGLIBEXECDIR However we want the lookup done for the exec'd command not stdbuf. Since we don't link against libstdbuf.so add it to PKGLIBEXECDIR rather than to LIBDIR. */ char const *const search_path[] = { program_path, PKGLIBEXECDIR, NULL }; char const *const *path = search_path; char *libstdbuf; while (true) { 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); ++path; if ( ! *path) error (EXIT_CANCELED, 0, _("failed to find %s"), quote (LIB_NAME)); } /* 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) { unsigned 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); } }