/* stdbuf -- setup the standard streams for a command                           This is the stdbuf utility
   Copyright (C) 2009-2018 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 <https://www.gnu.org/licenses/>.  */   The GNUv3 license
                                                                                
/* Written by Pádraig Brady.  */                                                
                                                                                
#include <config.h>                                                             Provides system specific information
#include <stdio.h>                                                              Provides standard I/O capability
#include <getopt.h>                                                             ...!includes auto-comment...
#include <sys/types.h>                                                          Provides system data types
#include <assert.h>                                                             ...!includes auto-comment...
                                                                                
#include "system.h"                                                             ...!includes auto-comment...
#include "die.h"                                                                ...!includes auto-comment...
#include "error.h"                                                              ...!includes auto-comment...
#include "filenamecat.h"                                                        ...!includes auto-comment...
#include "quote.h"                                                              ...!includes auto-comment...
#include "xreadlink.h"                                                          ...!includes auto-comment...
#include "xstrtol.h"                                                            ...!includes auto-comment...
#include "c-ctype.h"                                                            ...!includes auto-comment...
                                                                                
/* The official name of this program (e.g., no 'g' prefix).  */                 
#define PROGRAM_NAME "stdbuf"                                                   Line 35
#define LIB_NAME "libstdbuf.so" /* FIXME: don't hardcode  */                    Line 36
                                                                                
#define AUTHORS proper_name ("Padraig Brady")                                   Line 38
                                                                                
static char *program_path;                                                      Line 40
                                                                                
static struct                                                                   Line 42
{                                                                               
  size_t size;                                                                  Line 44
  int optc;                                                                     Line 45
  char *optarg;                                                                 Line 46
} stdbuf[3];                                                                    Line 47Block 1
                                                                                
static struct option const longopts[] =                                         Line 49
{                                                                               
  {"input", required_argument, NULL, 'i'},                                      Line 51
  {"output", required_argument, NULL, 'o'},                                     Line 52
  {"error", required_argument, NULL, 'e'},                                      Line 53
  {GETOPT_HELP_OPTION_DECL},                                                    Line 54
  {GETOPT_VERSION_OPTION_DECL},                                                 Line 55
  {NULL, 0, NULL, 0}                                                            Line 56
};                                                                              Block 2
                                                                                
/* 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                                                                      Line 65
parse_size (char const *str, size_t *size)                                      Line 66
{                                                                               
  uintmax_t tmp_size;                                                           Line 68
  enum strtol_error e = xstrtoumax (str, NULL, 10, &tmp_size, "EGkKMPTYZ0");    Line 69
  if (e == LONGINT_OK && SIZE_MAX < tmp_size)                                   Line 70
    e = LONGINT_OVERFLOW;                                                       Line 71
                                                                                
  if (e == LONGINT_OK)                                                          Line 73
    {                                                                           
      errno = 0;                                                                Line 75
      *size = tmp_size;                                                         Line 76
      return 0;                                                                 Line 77
    }                                                                           
                                                                                
  errno = (e == LONGINT_OVERFLOW ? EOVERFLOW : errno);                          Line 80
  return -1;                                                                    Line 81
}                                                                               Block 3
                                                                                
void                                                                            Line 84
usage (int status)                                                              Line 85
{                                                                               
  if (status != EXIT_SUCCESS)                                                   Line 87
    emit_try_help ();                                                           ...!common auto-comment...
  else                                                                          Line 89
    {                                                                           
      printf (_("Usage: %s OPTION... COMMAND\n"), program_name);                Line 91
      fputs (_("\                                                               Line 92
Run COMMAND, with modified buffering operations for its standard streams.\n\    Line 93
"), stdout);                                                                    Line 94
                                                                                
      emit_mandatory_arg_note ();                                               ...!common auto-comment...
                                                                                
      fputs (_("\                                                               Line 98
  -i, --input=MODE   adjust standard input stream buffering\n\                  Line 99
  -o, --output=MODE  adjust standard output stream buffering\n\                 Line 100
  -e, --error=MODE   adjust standard error stream buffering\n\                  Line 101
"), stdout);                                                                    Line 102
      fputs (HELP_OPTION_DESCRIPTION, stdout);                                  Line 103
      fputs (VERSION_OPTION_DESCRIPTION, stdout);                               Line 104
      fputs (_("\n\                                                             Line 105
If MODE is 'L' the corresponding stream will be line buffered.\n\               Line 106
This option is invalid with standard input.\n"), stdout);                       Line 107
      fputs (_("\n\                                                             Line 108
If MODE is '0' the corresponding stream will be unbuffered.\n\                  Line 109
"), stdout);                                                                    Line 110
      fputs (_("\n\                                                             Line 111
Otherwise MODE is a number which may be followed by one of the following:\n\    Line 112
KB 1000, K 1024, MB 1000*1000, M 1024*1024, and so on for G, T, P, E, Z, Y.\n\  Line 113
Binary prefixes can be used, too: KiB=K, MiB=M, and so on.\n\                   Line 114
In this case the corresponding stream will be fully buffered with the buffer\n\ Line 115
size set to MODE bytes.\n\                                                      Line 116
"), stdout);                                                                    Line 117
      fputs (_("\n\                                                             Line 118
NOTE: If COMMAND adjusts the buffering of its standard streams ('tee' does\n\   Line 119
for example) then that will override corresponding changes by 'stdbuf'.\n\      Line 120
Also some filters (like 'dd' and 'cat' etc.) don't use streams for I/O,\n\      Line 121
and are thus unaffected by 'stdbuf' settings.\n\                                Line 122
"), stdout);                                                                    Line 123
      emit_ancillary_info (PROGRAM_NAME);                                       Line 124
    }                                                                           
  exit (status);                                                                Line 126
}                                                                               Block 4
                                                                                
/* 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                                                                     Line 135
set_program_path (const char *arg)                                              Line 136
{                                                                               
  if (strchr (arg, '/'))        /* Use absolute or relative paths directly.  */ Line 138
    {                                                                           
      program_path = dir_name (arg);                                            Line 140
    }                                                                           
  else                                                                          Line 142
    {                                                                           
      char *path = xreadlink ("/proc/self/exe");                                Line 144...!syscalls auto-comment......!syscalls auto-comment...
      if (path)                                                                 Line 145
        program_path = dir_name (path);                                         Line 146
      else if ((path = getenv ("PATH")))                                        Line 147
        {                                                                       
          char *dir;                                                            Line 149
          path = xstrdup (path);                                                Line 150
          for (dir = strtok (path, ":"); dir != NULL; dir = strtok (NULL, ":")) Line 151
            {                                                                   
              char *candidate = file_name_concat (dir, arg, NULL);              Line 153
              if (access (candidate, X_OK) == 0)                                Line 154
                {                                                               
                  program_path = dir_name (candidate);                          Line 156
                  free (candidate);                                             Line 157
                  break;                                                        Line 158
                }                                                               
              free (candidate);                                                 Line 160
            }                                                                   
        }                                                                       
      free (path);                                                              Line 163
    }                                                                           
}                                                                               Block 5
                                                                                
static int                                                                      Line 167
optc_to_fileno (int c)                                                          Line 168
{                                                                               
  int ret = -1;                                                                 Line 170
                                                                                
  switch (c)                                                                    Line 172
    {                                                                           
    case 'e':                                                                   Line 174
      ret = STDERR_FILENO;                                                      Line 175
      break;                                                                    Line 176
    case 'i':                                                                   Line 177
      ret = STDIN_FILENO;                                                       Line 178
      break;                                                                    Line 179
    case 'o':                                                                   Line 180
      ret = STDOUT_FILENO;                                                      Line 181
      break;                                                                    Line 182
    }                                                                           
                                                                                
  return ret;                                                                   Line 185
}                                                                               Block 6
                                                                                
static void                                                                     Line 188
set_LD_PRELOAD (void)                                                           Line 189
{                                                                               
  int ret;                                                                      Line 191
#ifdef __APPLE__                                                                Line 192
  char const *preload_env = "DYLD_INSERT_LIBRARIES";                            Line 193
#else                                                                           Line 194
  char const *preload_env = "LD_PRELOAD";                                       Line 195
#endif                                                                          Line 196
  char *old_libs = getenv (preload_env);                                        Line 197
  char *LD_PRELOAD;                                                             Line 198
                                                                                
  /* 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.                                                     
                                                                                
     Note we could add "" as the penultimate item in the following list         
     to enable searching for libstdbuf.so in the default system lib paths.      
     However that would not indicate an error if libstdbuf.so was not found.    
     Also while this could support auto selecting the right arch in a multilib  
     environment, what we really want is to auto select based on the arch of the
     command being run, rather than that of stdbuf itself.  This is currently   
     not supported due to the unusual need for controlling the stdio buffering  
     of programs that are a different architecture to the default on the        
     system (and that of stdbuf itself).  */                                    
  char const *const search_path[] = {                                           Line 216
    program_path,                                                               Line 217
    PKGLIBEXECDIR,                                                              Line 218
    NULL                                                                        Line 219
  };                                                                            
                                                                                
  char const *const *path = search_path;                                        Line 222
  char *libstdbuf;                                                              Line 223
                                                                                
  while (true)                                                                  Line 225
    {                                                                           
      struct stat sb;                                                           Line 227
                                                                                
      if (!**path)              /* system default  */                           Line 229
        {                                                                       
          libstdbuf = xstrdup (LIB_NAME);                                       Line 231
          break;                                                                Line 232
        }                                                                       
      ret = asprintf (&libstdbuf, "%s/%s", *path, LIB_NAME);                    Line 234
      if (ret < 0)                                                              Line 235
        xalloc_die ();                                                          ...!common auto-comment...
      if (stat (libstdbuf, &sb) == 0)   /* file_exists  */                      Line 237...!syscalls auto-comment...
        break;                                                                  Line 238
      free (libstdbuf);                                                         Line 239
                                                                                
      ++path;                                                                   Line 241
      if ( ! *path)                                                             Line 242
        die (EXIT_CANCELED, 0, _("failed to find %s"), quote (LIB_NAME));       Line 243
    }                                                                           
                                                                                
  /* FIXME: Do we need to support libstdbuf.dll, c:, '\' separators etc?  */    
                                                                                
  if (old_libs)                                                                 Line 248
    ret = asprintf (&LD_PRELOAD, "%s=%s:%s", preload_env, old_libs, libstdbuf); Line 249
  else                                                                          Line 250
    ret = asprintf (&LD_PRELOAD, "%s=%s", preload_env, libstdbuf);              Line 251
                                                                                
  if (ret < 0)                                                                  Line 253
    xalloc_die ();                                                              ...!common auto-comment...
                                                                                
  free (libstdbuf);                                                             Line 256
                                                                                
  ret = putenv (LD_PRELOAD);                                                    Line 258
#ifdef __APPLE__                                                                Line 259
  if (ret == 0)                                                                 Line 260
    ret = setenv ("DYLD_FORCE_FLAT_NAMESPACE", "y", 1);                         Line 261
#endif                                                                          Line 262
                                                                                
  if (ret != 0)                                                                 Line 264
    {                                                                           
      die (EXIT_CANCELED, errno,                                                Line 266
           _("failed to update the environment with %s"),                       Line 267
           quote (LD_PRELOAD));                                                 Line 268
    }                                                                           
}                                                                               Block 7
                                                                                
/* Populate environ with _STDBUF_I=$MODE _STDBUF_O=$MODE _STDBUF_E=$MODE.       
   Return TRUE if any environment variables set.   */                           
                                                                                
static bool                                                                     Line 275
set_libstdbuf_options (void)                                                    Line 276
{                                                                               
  bool env_set = false;                                                         Line 278
                                                                                
  for (size_t i = 0; i < ARRAY_CARDINALITY (stdbuf); i++)                       Line 280
    {                                                                           
      if (stdbuf[i].optarg)                                                     Line 282
        {                                                                       
          char *var;                                                            Line 284
          int ret;                                                              Line 285
                                                                                
          if (*stdbuf[i].optarg == 'L')                                         Line 287
            ret = asprintf (&var, "%s%c=L", "_STDBUF_",                         Line 288
                            toupper (stdbuf[i].optc));                          Line 289
          else                                                                  Line 290
            ret = asprintf (&var, "%s%c=%" PRIuMAX, "_STDBUF_",                 Line 291
                            toupper (stdbuf[i].optc),                           Line 292
                            (uintmax_t) stdbuf[i].size);                        Line 293
          if (ret < 0)                                                          Line 294
            xalloc_die ();                                                      ...!common auto-comment...
                                                                                
          if (putenv (var) != 0)                                                Line 297
            {                                                                   
              die (EXIT_CANCELED, errno,                                        Line 299
                   _("failed to update the environment with %s"),               Line 300
                   quote (var));                                                Line 301
            }                                                                   
                                                                                
          env_set = true;                                                       Line 304
        }                                                                       
    }                                                                           
                                                                                
  return env_set;                                                               Line 308
}                                                                               Block 8
                                                                                
int                                                                             
main (int argc, char **argv)                                                    Line 312
{                                                                               
  int c;                                                                        Line 314
                                                                                
  initialize_main (&argc, &argv);                                               VMS-specific entry point handling wildcard expansion
  set_program_name (argv[0]);                                                   Retains program name and discards path
  setlocale (LC_ALL, "");                                                       Sets up internationalization (i18n)
  bindtextdomain (PACKAGE, LOCALEDIR);                                          Assigns i18n directorySets text domain for _() [gettext()] function
  textdomain (PACKAGE);                                                         Sets text domain for _() [gettext()] function
                                                                                
  initialize_exit_failure (EXIT_CANCELED);                                      Line 322
  atexit (close_stdout);                                                        Close stdout on exit (see gnulib)
                                                                                
  while ((c = getopt_long (argc, argv, "+i:o:e:", longopts, NULL)) != -1)       Line 325
    {                                                                           
      int opt_fileno;                                                           Line 327
                                                                                
      switch (c)                                                                Line 329
        {                                                                       
        /* Old McDonald had a farm ei...  */                                    
        case 'e':                                                               Line 332
        case 'i':                                                               Line 333
        case 'o':                                                               Line 334
          opt_fileno = optc_to_fileno (c);                                      Line 335
          assert (0 <= opt_fileno && opt_fileno < ARRAY_CARDINALITY (stdbuf));  Line 336
          stdbuf[opt_fileno].optc = c;                                          Line 337
          while (c_isspace (*optarg))                                           Line 338
            optarg++;                                                           Line 339
          stdbuf[opt_fileno].optarg = optarg;                                   Line 340
          if (c == 'i' && *optarg == 'L')                                       Line 341
            {                                                                   
              /* -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"));           Line 346
              usage (EXIT_CANCELED);                                            Line 347
            }                                                                   
                                                                                
          if (!STREQ (optarg, "L")                                              Line 350
              && parse_size (optarg, &stdbuf[opt_fileno].size) == -1)           Line 351
            die (EXIT_CANCELED, errno, _("invalid mode %s"), quote (optarg));   Line 352
                                                                                
          break;                                                                Line 354
                                                                                
        case_GETOPT_HELP_CHAR;                                                  Line 356
                                                                                
        case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);                       Line 358
                                                                                
        default:                                                                Line 360
          usage (EXIT_CANCELED);                                                Line 361
        }                                                                       
    }                                                                           
                                                                                
  argv += optind;                                                               Line 365
  argc -= optind;                                                               Line 366
                                                                                
  /* must specify at least 1 command.  */                                       
  if (argc < 1)                                                                 Line 369
    {                                                                           
      error (0, 0, _("missing operand"));                                       Line 371
      usage (EXIT_CANCELED);                                                    Line 372
    }                                                                           
                                                                                
  if (! set_libstdbuf_options ())                                               Line 375
    {                                                                           
      error (0, 0, _("you must specify a buffering mode option"));              Line 377
      usage (EXIT_CANCELED);                                                    Line 378
    }                                                                           
                                                                                
  /* Try to preload libstdbuf first from the same path as                       
     stdbuf is running from.  */                                                
  set_program_path (program_name);                                              Line 383
  if (!program_path)                                                            Line 384
    program_path = xstrdup (PKGLIBDIR);  /* Need to init to non-NULL.  */       Line 385
  set_LD_PRELOAD ();                                                            Line 386
  free (program_path);                                                          Line 387
                                                                                
  execvp (*argv, argv);                                                         Line 389
                                                                                
  int exit_status = errno == ENOENT ? EXIT_ENOENT : EXIT_CANNOT_INVOKE;         Line 391
  error (0, errno, _("failed to run command %s"), quote (argv[0]));             Line 392
  return exit_status;                                                           Line 393
}                                                                               Block 9