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