1 /* Substitution of environment variables in shell format strings.
2 Copyright (C) 2003-2007, 2012, 2018-2020 Free Software Foundation, Inc.
3 Written by Bruno Haible <bruno@clisp.org>, 2003.
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 3 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <https://www.gnu.org/licenses/>. */
17
18 #ifdef HAVE_CONFIG_H
19 # include <config.h>
20 #endif
21
22 #include <errno.h>
23 #include <getopt.h>
24 #include <stdbool.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <unistd.h>
29 #include <locale.h>
30
31 #include "noreturn.h"
32 #include "closeout.h"
33 #include "error.h"
34 #include "progname.h"
35 #include "relocatable.h"
36 #include "basename-lgpl.h"
37 #include "xalloc.h"
38 #include "propername.h"
39 #include "binary-io.h"
40 #include "gettext.h"
41
42 #define _(str) gettext (str)
43
44 /* If true, substitution shall be performed on all variables. */
45 static bool all_variables;
46
47 /* Long options. */
48 static const struct option long_options[] =
49 {
50 { "help", no_argument, NULL, 'h' },
51 { "variables", no_argument, NULL, 'v' },
52 { "version", no_argument, NULL, 'V' },
53 { NULL, 0, NULL, 0 }
54 };
55
56 /* Forward declaration of local functions. */
57 _GL_NORETURN_FUNC static void usage (int status);
58 static void print_variables (const char *string);
59 static void note_variables (const char *string);
60 static void subst_from_stdin (void);
61
62 int
main(int argc,char * argv[])63 main (int argc, char *argv[])
64 {
65 /* Default values for command line options. */
66 bool show_variables = false;
67 bool do_help = false;
68 bool do_version = false;
69
70 int opt;
71
72 /* Set program name for message texts. */
73 set_program_name (argv[0]);
74
75 /* Set locale via LC_ALL. */
76 setlocale (LC_ALL, "");
77
78 /* Set the text message domain. */
79 bindtextdomain (PACKAGE, relocate (LOCALEDIR));
80 textdomain (PACKAGE);
81
82 /* Ensure that write errors on stdout are detected. */
83 atexit (close_stdout);
84
85 /* Parse command line options. */
86 while ((opt = getopt_long (argc, argv, "hvV", long_options, NULL)) != EOF)
87 switch (opt)
88 {
89 case '\0': /* Long option. */
90 break;
91 case 'h':
92 do_help = true;
93 break;
94 case 'v':
95 show_variables = true;
96 break;
97 case 'V':
98 do_version = true;
99 break;
100 default:
101 usage (EXIT_FAILURE);
102 }
103
104 /* Version information is requested. */
105 if (do_version)
106 {
107 printf ("%s (GNU %s) %s\n", last_component (program_name),
108 PACKAGE, VERSION);
109 /* xgettext: no-wrap */
110 printf (_("Copyright (C) %s Free Software Foundation, Inc.\n\
111 License GPLv3+: GNU GPL version 3 or later <%s>\n\
112 This is free software: you are free to change and redistribute it.\n\
113 There is NO WARRANTY, to the extent permitted by law.\n\
114 "),
115 "2003-2020", "https://gnu.org/licenses/gpl.html");
116 printf (_("Written by %s.\n"), proper_name ("Bruno Haible"));
117 exit (EXIT_SUCCESS);
118 }
119
120 /* Help is requested. */
121 if (do_help)
122 usage (EXIT_SUCCESS);
123
124 if (argc - optind > 1)
125 error (EXIT_FAILURE, 0, _("too many arguments"));
126
127 /* Distinguish the two main operation modes. */
128 if (show_variables)
129 {
130 /* Output only the variables. */
131 switch (argc - optind)
132 {
133 case 1:
134 break;
135 case 0:
136 error (EXIT_FAILURE, 0, _("missing arguments"));
137 default:
138 abort ();
139 }
140
141 /* The result is most often used in shell `...` expressions.
142 Therefore, on native Windows, don't produce CR/LF newlines. */
143 set_binary_mode (STDOUT_FILENO, O_BINARY);
144
145 print_variables (argv[optind++]);
146 }
147 else
148 {
149 /* Actually perform the substitutions. */
150 switch (argc - optind)
151 {
152 case 1:
153 all_variables = false;
154 note_variables (argv[optind++]);
155 break;
156 case 0:
157 all_variables = true;
158 break;
159 default:
160 abort ();
161 }
162 subst_from_stdin ();
163 }
164
165 exit (EXIT_SUCCESS);
166 }
167
168
169 /* Display usage information and exit. */
170 static void
usage(int status)171 usage (int status)
172 {
173 if (status != EXIT_SUCCESS)
174 fprintf (stderr, _("Try '%s --help' for more information.\n"),
175 program_name);
176 else
177 {
178 /* xgettext: no-wrap */
179 printf (_("\
180 Usage: %s [OPTION] [SHELL-FORMAT]\n\
181 "), program_name);
182 printf ("\n");
183 /* xgettext: no-wrap */
184 printf (_("\
185 Substitutes the values of environment variables.\n"));
186 printf ("\n");
187 /* xgettext: no-wrap */
188 printf (_("\
189 Operation mode:\n"));
190 /* xgettext: no-wrap */
191 printf (_("\
192 -v, --variables output the variables occurring in SHELL-FORMAT\n"));
193 printf ("\n");
194 /* xgettext: no-wrap */
195 printf (_("\
196 Informative output:\n"));
197 /* xgettext: no-wrap */
198 printf (_("\
199 -h, --help display this help and exit\n"));
200 /* xgettext: no-wrap */
201 printf (_("\
202 -V, --version output version information and exit\n"));
203 printf ("\n");
204 /* xgettext: no-wrap */
205 printf (_("\
206 In normal operation mode, standard input is copied to standard output,\n\
207 with references to environment variables of the form $VARIABLE or ${VARIABLE}\n\
208 being replaced with the corresponding values. If a SHELL-FORMAT is given,\n\
209 only those environment variables that are referenced in SHELL-FORMAT are\n\
210 substituted; otherwise all environment variables references occurring in\n\
211 standard input are substituted.\n"));
212 printf ("\n");
213 /* xgettext: no-wrap */
214 printf (_("\
215 When --variables is used, standard input is ignored, and the output consists\n\
216 of the environment variables that are referenced in SHELL-FORMAT, one per line.\n"));
217 printf ("\n");
218 /* TRANSLATORS: The first placeholder is the web address of the Savannah
219 project of this package. The second placeholder is the bug-reporting
220 email address for this package. Please add _another line_ saying
221 "Report translation bugs to <...>\n" with the address for translation
222 bugs (typically your translation team's web or email address). */
223 printf(_("\
224 Report bugs in the bug tracker at <%s>\n\
225 or by email to <%s>.\n"),
226 "https://savannah.gnu.org/projects/gettext",
227 "bug-gettext@gnu.org");
228 }
229
230 exit (status);
231 }
232
233
234 /* Parse the string and invoke the callback each time a $VARIABLE or
235 ${VARIABLE} construct is seen, where VARIABLE is a nonempty sequence
236 of ASCII alphanumeric/underscore characters, starting with an ASCII
237 alphabetic/underscore character.
238 We allow only ASCII characters, to avoid dependencies w.r.t. the current
239 encoding: While "${\xe0}" looks like a variable access in ISO-8859-1
240 encoding, it doesn't look like one in the BIG5, BIG5-HKSCS, GBK, GB18030,
241 SHIFT_JIS, JOHAB encodings, because \xe0\x7d is a single character in these
242 encodings. */
243 static void
find_variables(const char * string,void (* callback)(const char * var_ptr,size_t var_len))244 find_variables (const char *string,
245 void (*callback) (const char *var_ptr, size_t var_len))
246 {
247 for (; *string != '\0';)
248 if (*string++ == '$')
249 {
250 const char *variable_start;
251 const char *variable_end;
252 bool valid;
253 char c;
254
255 if (*string == '{')
256 string++;
257
258 variable_start = string;
259 c = *string;
260 if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_')
261 {
262 do
263 c = *++string;
264 while ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')
265 || (c >= '0' && c <= '9') || c == '_');
266 variable_end = string;
267
268 if (variable_start[-1] == '{')
269 {
270 if (*string == '}')
271 {
272 string++;
273 valid = true;
274 }
275 else
276 valid = false;
277 }
278 else
279 valid = true;
280
281 if (valid)
282 callback (variable_start, variable_end - variable_start);
283 }
284 }
285 }
286
287
288 /* Print a variable to stdout, followed by a newline. */
289 static void
print_variable(const char * var_ptr,size_t var_len)290 print_variable (const char *var_ptr, size_t var_len)
291 {
292 fwrite (var_ptr, var_len, 1, stdout);
293 putchar ('\n');
294 }
295
296 /* Print the variables contained in STRING to stdout, each one followed by a
297 newline. */
298 static void
print_variables(const char * string)299 print_variables (const char *string)
300 {
301 find_variables (string, &print_variable);
302 }
303
304
305 /* Type describing list of immutable strings,
306 implemented using a dynamic array. */
307 typedef struct string_list_ty string_list_ty;
308 struct string_list_ty
309 {
310 const char **item;
311 size_t nitems;
312 size_t nitems_max;
313 };
314
315 /* Initialize an empty list of strings. */
316 static inline void
string_list_init(string_list_ty * slp)317 string_list_init (string_list_ty *slp)
318 {
319 slp->item = NULL;
320 slp->nitems = 0;
321 slp->nitems_max = 0;
322 }
323
324 /* Append a single string to the end of a list of strings. */
325 static inline void
string_list_append(string_list_ty * slp,const char * s)326 string_list_append (string_list_ty *slp, const char *s)
327 {
328 /* Grow the list. */
329 if (slp->nitems >= slp->nitems_max)
330 {
331 size_t nbytes;
332
333 slp->nitems_max = slp->nitems_max * 2 + 4;
334 nbytes = slp->nitems_max * sizeof (slp->item[0]);
335 slp->item = (const char **) xrealloc (slp->item, nbytes);
336 }
337
338 /* Add the string to the end of the list. */
339 slp->item[slp->nitems++] = s;
340 }
341
342 /* Compare two strings given by reference. */
343 static int
cmp_string(const void * pstr1,const void * pstr2)344 cmp_string (const void *pstr1, const void *pstr2)
345 {
346 const char *str1 = *(const char **)pstr1;
347 const char *str2 = *(const char **)pstr2;
348
349 return strcmp (str1, str2);
350 }
351
352 /* Sort a list of strings. */
353 static inline void
string_list_sort(string_list_ty * slp)354 string_list_sort (string_list_ty *slp)
355 {
356 if (slp->nitems > 0)
357 qsort (slp->item, slp->nitems, sizeof (slp->item[0]), cmp_string);
358 }
359
360 /* Test whether a string list contains a given string. */
361 static inline int
string_list_member(const string_list_ty * slp,const char * s)362 string_list_member (const string_list_ty *slp, const char *s)
363 {
364 size_t j;
365
366 for (j = 0; j < slp->nitems; ++j)
367 if (strcmp (slp->item[j], s) == 0)
368 return 1;
369 return 0;
370 }
371
372 /* Test whether a sorted string list contains a given string. */
373 static int
sorted_string_list_member(const string_list_ty * slp,const char * s)374 sorted_string_list_member (const string_list_ty *slp, const char *s)
375 {
376 size_t j1, j2;
377
378 j1 = 0;
379 j2 = slp->nitems;
380 if (j2 > 0)
381 {
382 /* Binary search. */
383 while (j2 - j1 > 1)
384 {
385 /* Here we know that if s is in the list, it is at an index j
386 with j1 <= j < j2. */
387 size_t j = (j1 + j2) >> 1;
388 int result = strcmp (slp->item[j], s);
389
390 if (result > 0)
391 j2 = j;
392 else if (result == 0)
393 return 1;
394 else
395 j1 = j + 1;
396 }
397 if (j2 > j1)
398 if (strcmp (slp->item[j1], s) == 0)
399 return 1;
400 }
401 return 0;
402 }
403
404 /* Destroy a list of strings. */
405 static inline void
string_list_destroy(string_list_ty * slp)406 string_list_destroy (string_list_ty *slp)
407 {
408 size_t j;
409
410 for (j = 0; j < slp->nitems; ++j)
411 free ((char *) slp->item[j]);
412 if (slp->item != NULL)
413 free (slp->item);
414 }
415
416
417 /* Set of variables on which to perform substitution.
418 Used only if !all_variables. */
419 static string_list_ty variables_set;
420
421 /* Adds a variable to variables_set. */
422 static void
note_variable(const char * var_ptr,size_t var_len)423 note_variable (const char *var_ptr, size_t var_len)
424 {
425 char *string = XNMALLOC (var_len + 1, char);
426 memcpy (string, var_ptr, var_len);
427 string[var_len] = '\0';
428
429 string_list_append (&variables_set, string);
430 }
431
432 /* Stores the variables occurring in the string in variables_set. */
433 static void
note_variables(const char * string)434 note_variables (const char *string)
435 {
436 string_list_init (&variables_set);
437 find_variables (string, ¬e_variable);
438 string_list_sort (&variables_set);
439 }
440
441
442 static int
do_getc()443 do_getc ()
444 {
445 int c = getc (stdin);
446
447 if (c == EOF)
448 {
449 if (ferror (stdin))
450 error (EXIT_FAILURE, errno,
451 _("error while reading \"%s\""), _("standard input"));
452 }
453
454 return c;
455 }
456
457 static inline void
do_ungetc(int c)458 do_ungetc (int c)
459 {
460 if (c != EOF)
461 ungetc (c, stdin);
462 }
463
464 /* Copies stdin to stdout, performing substitutions. */
465 static void
subst_from_stdin()466 subst_from_stdin ()
467 {
468 static char *buffer;
469 static size_t bufmax;
470 static size_t buflen;
471 int c;
472
473 for (;;)
474 {
475 c = do_getc ();
476 if (c == EOF)
477 break;
478 /* Look for $VARIABLE or ${VARIABLE}. */
479 if (c == '$')
480 {
481 bool opening_brace = false;
482 bool closing_brace = false;
483
484 c = do_getc ();
485 if (c == '{')
486 {
487 opening_brace = true;
488 c = do_getc ();
489 }
490 if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_')
491 {
492 bool valid;
493
494 /* Accumulate the VARIABLE in buffer. */
495 buflen = 0;
496 do
497 {
498 if (buflen >= bufmax)
499 {
500 bufmax = 2 * bufmax + 10;
501 buffer = xrealloc (buffer, bufmax);
502 }
503 buffer[buflen++] = c;
504
505 c = do_getc ();
506 }
507 while ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')
508 || (c >= '0' && c <= '9') || c == '_');
509
510 if (opening_brace)
511 {
512 if (c == '}')
513 {
514 closing_brace = true;
515 valid = true;
516 }
517 else
518 {
519 valid = false;
520 do_ungetc (c);
521 }
522 }
523 else
524 {
525 valid = true;
526 do_ungetc (c);
527 }
528
529 if (valid)
530 {
531 /* Terminate the variable in the buffer. */
532 if (buflen >= bufmax)
533 {
534 bufmax = 2 * bufmax + 10;
535 buffer = xrealloc (buffer, bufmax);
536 }
537 buffer[buflen] = '\0';
538
539 /* Test whether the variable shall be substituted. */
540 if (!all_variables
541 && !sorted_string_list_member (&variables_set, buffer))
542 valid = false;
543 }
544
545 if (valid)
546 {
547 /* Substitute the variable's value from the environment. */
548 const char *env_value = getenv (buffer);
549
550 if (env_value != NULL)
551 fputs (env_value, stdout);
552 }
553 else
554 {
555 /* Perform no substitution at all. Since the buffered input
556 contains no other '$' than at the start, we can just
557 output all the buffered contents. */
558 putchar ('$');
559 if (opening_brace)
560 putchar ('{');
561 fwrite (buffer, buflen, 1, stdout);
562 if (closing_brace)
563 putchar ('}');
564 }
565 }
566 else
567 {
568 do_ungetc (c);
569 putchar ('$');
570 if (opening_brace)
571 putchar ('{');
572 }
573 }
574 else
575 putchar (c);
576 }
577 }
578