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