(root)/
coreutils-9.4/
src/
stdbuf.c
       1  /* stdbuf -- setup the standard streams for a command
       2     Copyright (C) 2009-2023 Free Software Foundation, Inc.
       3  
       4     This program is free software: you can redistribute it and/or modify
       5     it under the terms of the GNU General Public License as published by
       6     the Free Software Foundation, either version 3 of the License, or
       7     (at your option) any later version.
       8  
       9     This program is distributed in the hope that it will be useful,
      10     but WITHOUT ANY WARRANTY; without even the implied warranty of
      11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      12     GNU General Public License for more details.
      13  
      14     You should have received a copy of the GNU General Public License
      15     along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
      16  
      17  /* Written by Pádraig Brady.  */
      18  
      19  #include <config.h>
      20  #include <stdio.h>
      21  #include <getopt.h>
      22  #include <sys/types.h>
      23  
      24  #include "system.h"
      25  #include "assure.h"
      26  #include "filenamecat.h"
      27  #include "quote.h"
      28  #include "xreadlink.h"
      29  #include "xstrtol.h"
      30  #include "c-ctype.h"
      31  
      32  /* The official name of this program (e.g., no 'g' prefix).  */
      33  #define PROGRAM_NAME "stdbuf"
      34  #define LIB_NAME "libstdbuf.so" /* FIXME: don't hardcode  */
      35  
      36  #define AUTHORS proper_name_lite ("Padraig Brady", "P\303\241draig Brady")
      37  
      38  static char *program_path;
      39  
      40  static struct
      41  {
      42    size_t size;
      43    int optc;
      44    char *optarg;
      45  } stdbuf[3];
      46  
      47  static struct option const longopts[] =
      48  {
      49    {"input", required_argument, nullptr, 'i'},
      50    {"output", required_argument, nullptr, 'o'},
      51    {"error", required_argument, nullptr, 'e'},
      52    {GETOPT_HELP_OPTION_DECL},
      53    {GETOPT_VERSION_OPTION_DECL},
      54    {nullptr, 0, nullptr, 0}
      55  };
      56  
      57  /* Set size to the value of STR, interpreted as a decimal integer,
      58     optionally multiplied by various values.
      59     Return -1 on error, 0 on success.
      60  
      61     This supports dd BLOCK size suffixes.
      62     Note we don't support dd's b=512, c=1, w=2 or 21x512MiB formats.  */
      63  static int
      64  parse_size (char const *str, size_t *size)
      65  {
      66    uintmax_t tmp_size;
      67    enum strtol_error e = xstrtoumax (str, nullptr, 10,
      68                                      &tmp_size, "EGkKMPQRTYZ0");
      69    if (e == LONGINT_OK && SIZE_MAX < tmp_size)
      70      e = LONGINT_OVERFLOW;
      71  
      72    if (e == LONGINT_OK)
      73      {
      74        errno = 0;
      75        *size = tmp_size;
      76        return 0;
      77      }
      78  
      79    errno = (e == LONGINT_OVERFLOW ? EOVERFLOW : errno);
      80    return -1;
      81  }
      82  
      83  void
      84  usage (int status)
      85  {
      86    if (status != EXIT_SUCCESS)
      87      emit_try_help ();
      88    else
      89      {
      90        printf (_("Usage: %s OPTION... COMMAND\n"), program_name);
      91        fputs (_("\
      92  Run COMMAND, with modified buffering operations for its standard streams.\n\
      93  "), stdout);
      94  
      95        emit_mandatory_arg_note ();
      96  
      97        fputs (_("\
      98    -i, --input=MODE   adjust standard input stream buffering\n\
      99    -o, --output=MODE  adjust standard output stream buffering\n\
     100    -e, --error=MODE   adjust standard error stream buffering\n\
     101  "), stdout);
     102        fputs (HELP_OPTION_DESCRIPTION, stdout);
     103        fputs (VERSION_OPTION_DESCRIPTION, stdout);
     104        fputs (_("\n\
     105  If MODE is 'L' the corresponding stream will be line buffered.\n\
     106  This option is invalid with standard input.\n"), stdout);
     107        fputs (_("\n\
     108  If MODE is '0' the corresponding stream will be unbuffered.\n\
     109  "), stdout);
     110        fputs (_("\n\
     111  Otherwise MODE is a number which may be followed by one of the following:\n\
     112  KB 1000, K 1024, MB 1000*1000, M 1024*1024, and so on for G,T,P,E,Z,Y,R,Q.\n\
     113  Binary prefixes can be used, too: KiB=K, MiB=M, and so on.\n\
     114  In this case the corresponding stream will be fully buffered with the buffer\n\
     115  size set to MODE bytes.\n\
     116  "), stdout);
     117        fputs (_("\n\
     118  NOTE: If COMMAND adjusts the buffering of its standard streams ('tee' does\n\
     119  for example) then that will override corresponding changes by 'stdbuf'.\n\
     120  Also some filters (like 'dd' and 'cat' etc.) don't use streams for I/O,\n\
     121  and are thus unaffected by 'stdbuf' settings.\n\
     122  "), stdout);
     123        emit_exec_status (PROGRAM_NAME);
     124        emit_ancillary_info (PROGRAM_NAME);
     125      }
     126    exit (status);
     127  }
     128  
     129  /* argv[0] can be anything really, but generally it contains
     130     the path to the executable or just a name if it was executed
     131     using $PATH. In the latter case to get the path we can:
     132     search getenv("PATH"), readlink("/prof/self/exe"), getenv("_"),
     133     dladdr(), pstat_getpathname(), etc.  */
     134  
     135  static void
     136  set_program_path (char const *arg)
     137  {
     138    if (strchr (arg, '/'))        /* Use absolute or relative paths directly.  */
     139      {
     140        program_path = dir_name (arg);
     141      }
     142    else
     143      {
     144        char *path = xreadlink ("/proc/self/exe");
     145        if (path)
     146          program_path = dir_name (path);
     147        else if ((path = getenv ("PATH")))
     148          {
     149            char *dir;
     150            path = xstrdup (path);
     151            for (dir = strtok (path, ":"); dir != nullptr;
     152                 dir = strtok (nullptr, ":"))
     153              {
     154                char *candidate = file_name_concat (dir, arg, nullptr);
     155                if (access (candidate, X_OK) == 0)
     156                  {
     157                    program_path = dir_name (candidate);
     158                    free (candidate);
     159                    break;
     160                  }
     161                free (candidate);
     162              }
     163          }
     164        free (path);
     165      }
     166  }
     167  
     168  static int
     169  optc_to_fileno (int c)
     170  {
     171    int ret = -1;
     172  
     173    switch (c)
     174      {
     175      case 'e':
     176        ret = STDERR_FILENO;
     177        break;
     178      case 'i':
     179        ret = STDIN_FILENO;
     180        break;
     181      case 'o':
     182        ret = STDOUT_FILENO;
     183        break;
     184      }
     185  
     186    return ret;
     187  }
     188  
     189  static void
     190  set_LD_PRELOAD (void)
     191  {
     192    int ret;
     193  #ifdef __APPLE__
     194    char const *preload_env = "DYLD_INSERT_LIBRARIES";
     195  #else
     196    char const *preload_env = "LD_PRELOAD";
     197  #endif
     198    char *old_libs = getenv (preload_env);
     199    char *LD_PRELOAD;
     200  
     201    /* Note this would auto add the appropriate search path for "libstdbuf.so":
     202       gcc stdbuf.c -Wl,-rpath,'$ORIGIN' -Wl,-rpath,$PKGLIBEXECDIR
     203       However we want the lookup done for the exec'd command not stdbuf.
     204  
     205       Since we don't link against libstdbuf.so add it to PKGLIBEXECDIR
     206       rather than to LIBDIR.
     207  
     208       Note we could add "" as the penultimate item in the following list
     209       to enable searching for libstdbuf.so in the default system lib paths.
     210       However that would not indicate an error if libstdbuf.so was not found.
     211       Also while this could support auto selecting the right arch in a multilib
     212       environment, what we really want is to auto select based on the arch of the
     213       command being run, rather than that of stdbuf itself.  This is currently
     214       not supported due to the unusual need for controlling the stdio buffering
     215       of programs that are a different architecture to the default on the
     216       system (and that of stdbuf itself).  */
     217    char const *const search_path[] = {
     218      program_path,
     219      PKGLIBEXECDIR,
     220      nullptr
     221    };
     222  
     223    char const *const *path = search_path;
     224    char *libstdbuf;
     225  
     226    while (true)
     227      {
     228        struct stat sb;
     229  
     230        if (!**path)              /* system default  */
     231          {
     232            libstdbuf = xstrdup (LIB_NAME);
     233            break;
     234          }
     235        ret = asprintf (&libstdbuf, "%s/%s", *path, LIB_NAME);
     236        if (ret < 0)
     237          xalloc_die ();
     238        if (stat (libstdbuf, &sb) == 0)   /* file_exists  */
     239          break;
     240        free (libstdbuf);
     241  
     242        ++path;
     243        if ( ! *path)
     244          error (EXIT_CANCELED, 0, _("failed to find %s"), quote (LIB_NAME));
     245      }
     246  
     247    /* FIXME: Do we need to support libstdbuf.dll, c:, '\' separators etc?  */
     248  
     249    if (old_libs)
     250      ret = asprintf (&LD_PRELOAD, "%s=%s:%s", preload_env, old_libs, libstdbuf);
     251    else
     252      ret = asprintf (&LD_PRELOAD, "%s=%s", preload_env, libstdbuf);
     253  
     254    if (ret < 0)
     255      xalloc_die ();
     256  
     257    free (libstdbuf);
     258  
     259    ret = putenv (LD_PRELOAD);
     260  #ifdef __APPLE__
     261    if (ret == 0)
     262      ret = setenv ("DYLD_FORCE_FLAT_NAMESPACE", "y", 1);
     263  #endif
     264  
     265    if (ret != 0)
     266      error (EXIT_CANCELED, errno,
     267             _("failed to update the environment with %s"),
     268             quote (LD_PRELOAD));
     269  }
     270  
     271  /* Populate environ with _STDBUF_I=$MODE _STDBUF_O=$MODE _STDBUF_E=$MODE.
     272     Return TRUE if any environment variables set.   */
     273  
     274  static bool
     275  set_libstdbuf_options (void)
     276  {
     277    bool env_set = false;
     278  
     279    for (size_t i = 0; i < ARRAY_CARDINALITY (stdbuf); i++)
     280      {
     281        if (stdbuf[i].optarg)
     282          {
     283            char *var;
     284            int ret;
     285  
     286            if (*stdbuf[i].optarg == 'L')
     287              ret = asprintf (&var, "%s%c=L", "_STDBUF_",
     288                              toupper (stdbuf[i].optc));
     289            else
     290              ret = asprintf (&var, "%s%c=%" PRIuMAX, "_STDBUF_",
     291                              toupper (stdbuf[i].optc),
     292                              (uintmax_t) stdbuf[i].size);
     293            if (ret < 0)
     294              xalloc_die ();
     295  
     296            if (putenv (var) != 0)
     297              error (EXIT_CANCELED, errno,
     298                     _("failed to update the environment with %s"),
     299                     quote (var));
     300  
     301            env_set = true;
     302          }
     303      }
     304  
     305    return env_set;
     306  }
     307  
     308  int
     309  main (int argc, char **argv)
     310  {
     311    int c;
     312  
     313    initialize_main (&argc, &argv);
     314    set_program_name (argv[0]);
     315    setlocale (LC_ALL, "");
     316    bindtextdomain (PACKAGE, LOCALEDIR);
     317    textdomain (PACKAGE);
     318  
     319    initialize_exit_failure (EXIT_CANCELED);
     320    atexit (close_stdout);
     321  
     322    while ((c = getopt_long (argc, argv, "+i:o:e:", longopts, nullptr)) != -1)
     323      {
     324        int opt_fileno;
     325  
     326        switch (c)
     327          {
     328          /* Old McDonald had a farm ei...  */
     329          case 'e':
     330          case 'i':
     331          case 'o':
     332            opt_fileno = optc_to_fileno (c);
     333            affirm (0 <= opt_fileno && opt_fileno < ARRAY_CARDINALITY (stdbuf));
     334            stdbuf[opt_fileno].optc = c;
     335            while (c_isspace (*optarg))
     336              optarg++;
     337            stdbuf[opt_fileno].optarg = optarg;
     338            if (c == 'i' && *optarg == 'L')
     339              {
     340                /* -oL will be by far the most common use of this utility,
     341                   but one could easily think -iL might have the same affect,
     342                   so disallow it as it could be confusing.  */
     343                error (0, 0, _("line buffering stdin is meaningless"));
     344                usage (EXIT_CANCELED);
     345              }
     346  
     347            if (!STREQ (optarg, "L")
     348                && parse_size (optarg, &stdbuf[opt_fileno].size) == -1)
     349              error (EXIT_CANCELED, errno, _("invalid mode %s"), quote (optarg));
     350  
     351            break;
     352  
     353          case_GETOPT_HELP_CHAR;
     354  
     355          case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
     356  
     357          default:
     358            usage (EXIT_CANCELED);
     359          }
     360      }
     361  
     362    argv += optind;
     363    argc -= optind;
     364  
     365    /* must specify at least 1 command.  */
     366    if (argc < 1)
     367      {
     368        error (0, 0, _("missing operand"));
     369        usage (EXIT_CANCELED);
     370      }
     371  
     372    if (! set_libstdbuf_options ())
     373      {
     374        error (0, 0, _("you must specify a buffering mode option"));
     375        usage (EXIT_CANCELED);
     376      }
     377  
     378    /* Try to preload libstdbuf first from the same path as
     379       stdbuf is running from.  */
     380    set_program_path (program_name);
     381    if (!program_path)
     382      program_path = xstrdup (PKGLIBDIR);  /* Need to init to non-null.  */
     383    set_LD_PRELOAD ();
     384    free (program_path);
     385  
     386    execvp (*argv, argv);
     387  
     388    int exit_status = errno == ENOENT ? EXIT_ENOENT : EXIT_CANNOT_INVOKE;
     389    error (0, errno, _("failed to run command %s"), quote (argv[0]));
     390    return exit_status;
     391  }