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