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