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