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