1 /* libstdbuf -- a shared lib to preload to setup stdio buffering 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.  LD_PRELOAD idea from Brian Dessent.  */
18 
19 #include <config.h>
20 #include <stdio.h>
21 #include <stdint.h>
22 #include "system.h"
23 
24 /* Deactivate config.h's "rpl_"-prefixed definitions, since we don't
25    link gnulib here, and the replacements aren't needed.  */
26 #undef fprintf
27 #undef free
28 #undef malloc
29 #undef strtoumax
30 
31 /* Note currently for glibc (2.3.5) the following call does not change
32    the buffer size, and more problematically does not give any indication
33    that the new size request was ignored:
34 
35        setvbuf (stdout, nullptr, _IOFBF, 8192);
36 
37    The ISO C99 standard section 7.19.5.6 on the setvbuf function says:
38 
39    ... If buf is not a null pointer, the array it points to _may_ be used
40    instead of a buffer allocated by the setvbuf function and the argument
41    size specifies the size of the array; otherwise, size _may_ determine
42    the size of a buffer allocated by the setvbuf function. ...
43 
44    Obviously some interpret the above to mean setvbuf(....,size)
45    is only a hint from the application which I don't agree with.
46 
47    FreeBSD's libc seems more sensible in this regard. From the man page:
48 
49    The size argument may be given as zero to obtain deferred optimal-size
50    buffer allocation as usual.  If it is not zero, then except for
51    unbuffered files, the buf argument should point to a buffer at least size
52    bytes long; this buffer will be used instead of the current buffer.  (If
53    the size argument is not zero but buf is null, a buffer of the given size
54    will be allocated immediately, and released on close.  This is an extension
55    to ANSI C; portable code should use a size of 0 with any null buffer.)
56    --------------------
57    Another issue is that on glibc-2.7 the following doesn't buffer
58    the first write if it's greater than 1 byte.
59 
60        setvbuf(stdout,buf,_IOFBF,127);
61 
62    Now the POSIX standard says that "allocating a buffer of size bytes does
63    not necessarily imply that all of size bytes are used for the buffer area".
64    However I think it's just a buggy implementation due to the various
65    inconsistencies with write sizes and subsequent writes.  */
66 
67 static char const *
fileno_to_name(const int fd)68 fileno_to_name (const int fd)
69 {
70   char const *ret = nullptr;
71 
72   switch (fd)
73     {
74     case 0:
75       ret = "stdin";
76       break;
77     case 1:
78       ret = "stdout";
79       break;
80     case 2:
81       ret = "stderr";
82       break;
83     default:
84       ret = "unknown";
85       break;
86     }
87 
88   return ret;
89 }
90 
91 static void
apply_mode(FILE * stream,char const * mode)92 apply_mode (FILE *stream, char const *mode)
93 {
94   char *buf = nullptr;
95   int setvbuf_mode;
96   uintmax_t size = 0;
97 
98   if (*mode == '0')
99     setvbuf_mode = _IONBF;
100   else if (*mode == 'L')
101     setvbuf_mode = _IOLBF;      /* FIXME: should we allow 1ML  */
102   else
103     {
104       setvbuf_mode = _IOFBF;
105       char *mode_end;
106       size = strtoumax (mode, &mode_end, 10);
107       if (size == 0 || *mode_end)
108         {
109           fprintf (stderr, _("invalid buffering mode %s for %s\n"),
110                    mode, fileno_to_name (fileno (stream)));
111           return;
112         }
113 
114       buf = size <= SIZE_MAX ? malloc (size) : nullptr;
115       if (!buf)
116         {
117           /* We could defer the allocation to libc, however since
118              glibc currently ignores the combination of null buffer
119              with non zero size, we'll fail here.  */
120           fprintf (stderr,
121                    _("failed to allocate a %ju byte stdio buffer\n"),
122                    size);
123           return;
124         }
125       /* buf will be freed by fclose.  */
126     }
127 
128   if (setvbuf (stream, buf, setvbuf_mode, size) != 0)
129     {
130       fprintf (stderr, _("could not set buffering of %s to mode %s\n"),
131                fileno_to_name (fileno (stream)), mode);
132       free (buf);
133     }
134 }
135 
136 /* Use __attribute to avoid elision of __attribute__ on SUNPRO_C etc.  */
137 static void __attribute ((constructor))
stdbuf(void)138 stdbuf (void)
139 {
140   char *e_mode = getenv ("_STDBUF_E");
141   char *i_mode = getenv ("_STDBUF_I");
142   char *o_mode = getenv ("_STDBUF_O");
143   if (e_mode) /* Do first so can write errors to stderr  */
144     apply_mode (stderr, e_mode);
145   if (i_mode)
146     apply_mode (stdin, i_mode);
147   if (o_mode)
148     apply_mode (stdout, o_mode);
149 }
150