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