1/* 2 * sh-i18n--envsubst.c - a stripped-down version of gettext's envsubst(1) 3 * 4 * Copyright (C) 2010 Ævar Arnfjörð Bjarmason 5 * 6 * This is a modified version of 7 * 67d0871a8c:gettext-runtime/src/envsubst.c from the gettext.git 8 * repository. It has been stripped down to only implement the 9 * envsubst(1) features that we need in the git-sh-i18n fallbacks. 10 * 11 * The "Close standard error" part in main() is from 12 * 8dac033df0:gnulib-local/lib/closeout.c. The copyright notices for 13 * both files are reproduced immediately below. 14 */ 15 16#include "git-compat-util.h" 17#include "trace2.h" 18 19/* Substitution of environment variables in shell format strings. 20 Copyright (C) 2003-2007 Free Software Foundation, Inc. 21 Written by Bruno Haible <bruno@clisp.org>, 2003. 22 23 This program is free software; you can redistribute it and/or modify 24 it under the terms of the GNU General Public License as published by 25 the Free Software Foundation; either version 2, or (at your option) 26 any later version. 27 28 This program is distributed in the hope that it will be useful, 29 but WITHOUT ANY WARRANTY; without even the implied warranty of 30 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 31 GNU General Public License for more details. 32 33 You should have received a copy of the GNU General Public License 34 along with this program; if not, see <http://www.gnu.org/licenses/>. */ 35 36/* closeout.c - close standard output and standard error 37 Copyright (C) 1998-2007 Free Software Foundation, Inc. 38 39 This program is free software; you can redistribute it and/or modify 40 it under the terms of the GNU General Public License as published by 41 the Free Software Foundation; either version 2, or (at your option) 42 any later version. 43 44 This program is distributed in the hope that it will be useful, 45 but WITHOUT ANY WARRANTY; without even the implied warranty of 46 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 47 GNU General Public License for more details. 48 49 You should have received a copy of the GNU General Public License 50 along with this program; if not, see <http://www.gnu.org/licenses/>. */ 51 52#include <errno.h> 53#include <stdio.h> 54#include <stdlib.h> 55#include <string.h> 56 57/* If true, substitution shall be performed on all variables. */ 58static unsigned short int all_variables; 59 60/* Forward declaration of local functions. */ 61static void print_variables (const char *string); 62static void note_variables (const char *string); 63static void subst_from_stdin (void); 64 65int 66cmd_main (int argc, const char *argv[]) 67{ 68 /* Default values for command line options. */ 69 /* unsigned short int show_variables = 0; */ 70 71 trace2_cmd_name("sh-i18n--envsubst"); 72 73 switch (argc) 74 { 75 case 1: 76 error ("we won't substitute all variables on stdin for you"); 77 break; 78 /* 79 all_variables = 1; 80 subst_from_stdin (); 81 */ 82 case 2: 83 /* echo '$foo and $bar' | git sh-i18n--envsubst --variables '$foo and $bar' */ 84 all_variables = 0; 85 note_variables (argv[1]); 86 subst_from_stdin (); 87 break; 88 case 3: 89 /* git sh-i18n--envsubst --variables '$foo and $bar' */ 90 if (strcmp(argv[1], "--variables")) 91 error ("first argument must be --variables when two are given"); 92 /* show_variables = 1; */ 93 print_variables (argv[2]); 94 break; 95 default: 96 error ("too many arguments"); 97 break; 98 } 99 100 /* Close standard error. This is simpler than fwriteerror_no_ebadf, because 101 upon failure we don't need an errno - all we can do at this point is to 102 set an exit status. */ 103 errno = 0; 104 if (ferror (stderr) || fflush (stderr)) 105 { 106 fclose (stderr); 107 exit (EXIT_FAILURE); 108 } 109 if (fclose (stderr) && errno != EBADF) 110 exit (EXIT_FAILURE); 111 112 exit (EXIT_SUCCESS); 113} 114 115/* Parse the string and invoke the callback each time a $VARIABLE or 116 ${VARIABLE} construct is seen, where VARIABLE is a nonempty sequence 117 of ASCII alphanumeric/underscore characters, starting with an ASCII 118 alphabetic/underscore character. 119 We allow only ASCII characters, to avoid dependencies w.r.t. the current 120 encoding: While "${\xe0}" looks like a variable access in ISO-8859-1 121 encoding, it doesn't look like one in the BIG5, BIG5-HKSCS, GBK, GB18030, 122 SHIFT_JIS, JOHAB encodings, because \xe0\x7d is a single character in these 123 encodings. */ 124static void 125find_variables (const char *string, 126 void (*callback) (const char *var_ptr, size_t var_len)) 127{ 128 for (; *string != '\0';) 129 if (*string++ == '$') 130 { 131 const char *variable_start; 132 const char *variable_end; 133 unsigned short int valid; 134 char c; 135 136 if (*string == '{') 137 string++; 138 139 variable_start = string; 140 c = *string; 141 if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_') 142 { 143 do 144 c = *++string; 145 while ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') 146 || (c >= '0' && c <= '9') || c == '_'); 147 variable_end = string; 148 149 if (variable_start[-1] == '{') 150 { 151 if (*string == '}') 152 { 153 string++; 154 valid = 1; 155 } 156 else 157 valid = 0; 158 } 159 else 160 valid = 1; 161 162 if (valid) 163 callback (variable_start, variable_end - variable_start); 164 } 165 } 166} 167 168 169/* Print a variable to stdout, followed by a newline. */ 170static void 171print_variable (const char *var_ptr, size_t var_len) 172{ 173 fwrite (var_ptr, var_len, 1, stdout); 174 putchar ('\n'); 175} 176 177/* Print the variables contained in STRING to stdout, each one followed by a 178 newline. */ 179static void 180print_variables (const char *string) 181{ 182 find_variables (string, &print_variable); 183} 184 185 186/* Type describing list of immutable strings, 187 implemented using a dynamic array. */ 188typedef struct string_list_ty string_list_ty; 189struct string_list_ty 190{ 191 const char **item; 192 size_t nitems; 193 size_t nitems_max; 194}; 195 196/* Initialize an empty list of strings. */ 197static inline void 198string_list_init (string_list_ty *slp) 199{ 200 slp->item = NULL; 201 slp->nitems = 0; 202 slp->nitems_max = 0; 203} 204 205/* Append a single string to the end of a list of strings. */ 206static inline void 207string_list_append (string_list_ty *slp, const char *s) 208{ 209 /* Grow the list. */ 210 if (slp->nitems >= slp->nitems_max) 211 { 212 slp->nitems_max = slp->nitems_max * 2 + 4; 213 REALLOC_ARRAY(slp->item, slp->nitems_max); 214 } 215 216 /* Add the string to the end of the list. */ 217 slp->item[slp->nitems++] = s; 218} 219 220/* Compare two strings given by reference. */ 221static int 222cmp_string (const void *pstr1, const void *pstr2) 223{ 224 const char *str1 = *(const char **)pstr1; 225 const char *str2 = *(const char **)pstr2; 226 227 return strcmp (str1, str2); 228} 229 230/* Sort a list of strings. */ 231static inline void 232string_list_sort (string_list_ty *slp) 233{ 234 QSORT(slp->item, slp->nitems, cmp_string); 235} 236 237/* Test whether a sorted string list contains a given string. */ 238static int 239sorted_string_list_member (const string_list_ty *slp, const char *s) 240{ 241 size_t j1, j2; 242 243 j1 = 0; 244 j2 = slp->nitems; 245 if (j2 > 0) 246 { 247 /* Binary search. */ 248 while (j2 - j1 > 1) 249 { 250 /* Here we know that if s is in the list, it is at an index j 251 with j1 <= j < j2. */ 252 size_t j = (j1 + j2) >> 1; 253 int result = strcmp (slp->item[j], s); 254 255 if (result > 0) 256 j2 = j; 257 else if (result == 0) 258 return 1; 259 else 260 j1 = j + 1; 261 } 262 if (j2 > j1) 263 if (strcmp (slp->item[j1], s) == 0) 264 return 1; 265 } 266 return 0; 267} 268 269 270/* Set of variables on which to perform substitution. 271 Used only if !all_variables. */ 272static string_list_ty variables_set; 273 274/* Adds a variable to variables_set. */ 275static void 276note_variable (const char *var_ptr, size_t var_len) 277{ 278 char *string = xmemdupz (var_ptr, var_len); 279 280 string_list_append (&variables_set, string); 281} 282 283/* Stores the variables occurring in the string in variables_set. */ 284static void 285note_variables (const char *string) 286{ 287 string_list_init (&variables_set); 288 find_variables (string, ¬e_variable); 289 string_list_sort (&variables_set); 290} 291 292 293static int 294do_getc (void) 295{ 296 int c = getc (stdin); 297 298 if (c == EOF) 299 { 300 if (ferror (stdin)) 301 error ("error while reading standard input"); 302 } 303 304 return c; 305} 306 307static inline void 308do_ungetc (int c) 309{ 310 if (c != EOF) 311 ungetc (c, stdin); 312} 313 314/* Copies stdin to stdout, performing substitutions. */ 315static void 316subst_from_stdin (void) 317{ 318 static char *buffer; 319 static size_t bufmax; 320 static size_t buflen; 321 int c; 322 323 for (;;) 324 { 325 c = do_getc (); 326 if (c == EOF) 327 break; 328 /* Look for $VARIABLE or ${VARIABLE}. */ 329 if (c == '$') 330 { 331 unsigned short int opening_brace = 0; 332 unsigned short int closing_brace = 0; 333 334 c = do_getc (); 335 if (c == '{') 336 { 337 opening_brace = 1; 338 c = do_getc (); 339 } 340 if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_') 341 { 342 unsigned short int valid; 343 344 /* Accumulate the VARIABLE in buffer. */ 345 buflen = 0; 346 do 347 { 348 if (buflen >= bufmax) 349 { 350 bufmax = 2 * bufmax + 10; 351 buffer = xrealloc (buffer, bufmax); 352 } 353 buffer[buflen++] = c; 354 355 c = do_getc (); 356 } 357 while ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') 358 || (c >= '0' && c <= '9') || c == '_'); 359 360 if (opening_brace) 361 { 362 if (c == '}') 363 { 364 closing_brace = 1; 365 valid = 1; 366 } 367 else 368 { 369 valid = 0; 370 do_ungetc (c); 371 } 372 } 373 else 374 { 375 valid = 1; 376 do_ungetc (c); 377 } 378 379 if (valid) 380 { 381 /* Terminate the variable in the buffer. */ 382 if (buflen >= bufmax) 383 { 384 bufmax = 2 * bufmax + 10; 385 buffer = xrealloc (buffer, bufmax); 386 } 387 buffer[buflen] = '\0'; 388 389 /* Test whether the variable shall be substituted. */ 390 if (!all_variables 391 && !sorted_string_list_member (&variables_set, buffer)) 392 valid = 0; 393 } 394 395 if (valid) 396 { 397 /* Substitute the variable's value from the environment. */ 398 const char *env_value = getenv (buffer); 399 400 if (env_value != NULL) 401 fputs (env_value, stdout); 402 } 403 else 404 { 405 /* Perform no substitution at all. Since the buffered input 406 contains no other '$' than at the start, we can just 407 output all the buffered contents. */ 408 putchar ('$'); 409 if (opening_brace) 410 putchar ('{'); 411 fwrite (buffer, buflen, 1, stdout); 412 if (closing_brace) 413 putchar ('}'); 414 } 415 } 416 else 417 { 418 do_ungetc (c); 419 putchar ('$'); 420 if (opening_brace) 421 putchar ('{'); 422 } 423 } 424 else 425 putchar (c); 426 } 427}